Search code examples
c#stringencryptionsecurestring

How do I properly emit binary data from a SecureString, so that it can later be converted to a string?


I have strings of sensitive information that I need to collect from my users. I am using a WPF PasswordBox to request this information. For the uninitiated, the PasswordBox control provides a SecurePassword property which is a SecureString object rather than an insecure string object. Within my application, the data from the PasswordBox gets passed as a SecureString to an encryption method.

What I need to be able to do is marshal the data to a byte array that essentially represents a .Net string value without first converting the data to a .Net string. More specifically, given a SecureString with a value such as...

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()_-+={[}]|:;"'<,>.?/ ≈篭母

...how can I convert it to a byte array that is the equivalent a .Net string that's been serialized and written to a stream with a StreamWriter?

By using Marshal.SecureStringToCoTaskMemUnicode(...) I am able to do this with more traditional, western text. However, when I created the above text string using additional, not-typical characters and a string of Japanese text (see the last few bolded characters) my method of getting a Unicode byte array assigned to the IntPtr position doesn't seem to properly work anymore.

How can I emit the data of a SecureString in a secure way such that the returned byte data is structured the same as the byte data of a standard .Net string, serialized to binary output?

NOTE

Please ignore all security concerns at the moment. I am working on making various security upgrades to my application. For now, I need to use a SecureString for getting the sensitive data to the encryptor. The decryptor (for now) will still need to decrypt this data to string values, which is why I need to some how serialize the data in the the SecureString to a binary format similar to the binary format of the string object.

I agree that this approach is a bit unfortunate, however, I'm having to make incremental improvements on an existing application, and the first phase is locking down the data in SecureString objects from the user to the encryptor.


Solution

  • If you need to write secure string to stream, I'd suggest to create method like this:

    public static class Extensions {
        public static void WriteSecure(this StreamWriter writer, SecureString sec) {
            int length = sec.Length;
            if (length == 0)
                return;
            IntPtr ptr = Marshal.SecureStringToBSTR(sec);
            try {
                // each char in that string is 2 bytes, not one (it's UTF-16 string)
                for (int i = 0; i < length * 2; i += 2) {
                    // so use ReadInt16 and convert resulting "short" to char
                    var ch = Convert.ToChar(Marshal.ReadInt16(ptr + i));
                    // write
                    writer.Write(ch);
                }
            }
            finally {
                // don't forget to zero memory
                Marshal.ZeroFreeBSTR(ptr);
            }
        }
    }
    

    If you really need byte array - you can reuse this method too:

    byte[] result;
    using (var ms = new MemoryStream()) {
        using (var writer = new StreamWriter(ms)) {
            writer.WriteSecure(secureString);
        }
        result = ms.ToArray();
    }
    

    Though method from first comment might be a bit more pefomant (not sure if that's important for you).