Best way to retrieve the value of a SecureString

4.4k views Asked by At

I'm trying to build an automated tool that goes out and checks recent transactions on my checking account (spying on my wife's purchases lol). The tool posts my username and password over SSL and then scrapes the bank page contents for transactions. Everything is sent over SSL of course but in order to make it automated I need to store my bank username and password somewhere.

So I started down the rabbit hole of .NET strings, security, and SecureString. I made a form to input my username and password into a SecureString (character by character) making sure not to use ANY System.String's. Then I retrieve a char[] from the SecureString, get a byte[] from it, zero the char[], encrypt the byte[] using the DPAPI and a salt string built in code, convert the encrypted byte[] to a Base64 System.String, zero the byte[]'s, and save the crypto string to an application setting.

The question was, how to I get I get it back into POST data in a HttpWebRequest without making it a System.String. What I came up with was the following function (which also URL encodes the characters without making strings).

/// <summary>
/// Retrieves, (and then zero's,) a character array from the SecureString.
/// Then optionally URL encodes the characters and returns an encoded byte array.
/// Make sure to zero out the byte array when you're done with it.
/// </summary>
/// <param name="secure">The SecureString to be retrieved.</param>
/// <param name="encodingFormat">The encoding format used to retrieve the byte array. The default is UTF-8.</param>
/// <param name="urlEncode">If true will URL encode characters which are invalid for URL's.</param>
/// <returns></returns>
public static byte[] ToByteArray(this SecureString secure, System.Text.Encoding encodingFormat, bool urlEncode)
{
    if (secure == null) throw new ArgumentNullException("secure");

    if (encodingFormat == null)
        encodingFormat = System.Text.Encoding.UTF8;

    char[] chars = new char[secure.Length];
    byte[] bytesToReturn;

    // copy the SecureString data into an unmanaged char array and get a pointer to it
    IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(secure);

    try
    {
        // copy the unmanaged char array into a managed char array
        Marshal.Copy(ptr, chars, 0, secure.Length);
    }
    finally
    {
        Marshal.ZeroFreeCoTaskMemUnicode(ptr);
    }

    if (false == urlEncode)
    {
        bytesToReturn = encodingFormat.GetBytes(chars);
    }
    else
    {
        List<int> unsafeUrlCharIntValues = new List<int>();
        unsafeUrlCharIntValues.AddRange(Enumerable.Range(32, 16)); //   32-47   Reserved Characters:    ' '!?#$%&'()*+,-./
        unsafeUrlCharIntValues.AddRange(Enumerable.Range(58, 7)); //    58-64   Reserved Characters:    :;<=>?@
        unsafeUrlCharIntValues.AddRange(Enumerable.Range(91, 6)); //    91-96   Reserved Characters:    [\]^_`
        unsafeUrlCharIntValues.AddRange(Enumerable.Range(123, 4)); //   123-126 Reserved Characters:    {|}~

        // Get a count of the unsafe URL characters to see if any processing is required and how many extra char's we'll need in the array.
        int unsafeCharCount = 0;
        for (int i = 0; i < chars.Length; i++)
            if (unsafeUrlCharIntValues.Contains((int)chars[i]))
                unsafeCharCount++;

        if (unsafeCharCount < 1)
        {
            bytesToReturn = encodingFormat.GetBytes(chars);
        }
        else
        {
            char[] encodedChars = EncodeChars(chars);
            bytesToReturn = encodingFormat.GetBytes(encodedChars);

            // zero out encoded chars for security
            for (int i = 0; i < encodedChars.Length; i++)
                encodedChars[i] = '\0';
        }
    }

    // zero out chars
    for (int i = 0; i < chars.Length; i++)
        chars[i] = '\0';

    return bytesToReturn;
}

I zero out the char[]'s after I'm done with them. I also make sure to zero out the returned byte[] after I've written them to the HttpWebRequest stream. Is this secure or will the GC make copies of the char[] and byte[] objects? Is this any better than using System.String's? I've read other posts saying to use unsafe code and do everything in unmanaged memory but I still need to get a byte[] to my HttpWebRequest in the end.

0

There are 0 answers