Making Strings More Secure

The standard System.String has never been a very secure solution for storing sensitive strings such as passwords or credit card numbers.  Using a string for this purpose has numerous problems, including:

  • It's not pinned, so the garbage collector can move it around at will leaving several copies in memory
  • It's not encrypted, so anyone who can read your process' memory will be able to see the value of the string easily.  Also, if your process gets swapped out to disk, the unencrypted contents of the string will be sitting in your swap file.
  • It's not mutable, so whenever you need to modify it, there will be the old version and the new version both in memory
  • Since it's not mutable, there's no effective way to clear it out when you're done using it

Before Whidbey, the recommended solution was to use a byte array.  Since byte arrays can be pinned down, encrypted, and zeroed out, many of the above concerns were solved.  Whidbey will be introducing a new class, SecureString, that helps to make this all easier for you.

SecureStrings are held in encrypted memory by the CLR (using DPAPI), and are only unencrypted when they are accessed.  This limits the amount of time that your string is in plaintext for an attacker to see.  Since SecureString uses DPAPI to help secure your data, it's not available on Windows 98, ME, or Windows 2000 with anything less than service pack 3.

The garbage collector will not move the encrypted string around in memory, so you never have to worry about multiple copies of your string sitting in your address space (unless you make those copies).  SecureString also implements IDisposable, and when it's disposed (or finalized if you forget to dispose it), the memory that was used to hold your encrypted string will be zeroed out (several times actually).  They also provide a feature that lets you lock them down as read only, preventing other code from modifying your string.

You can create a SecureString with a pointer to a character array, and the length of that array.  When constructed this way, the SecureString will make a copy of your array, allowing you to zero out your insecure copy.  A SecureString can also be constructed without an existing character array, and data can be copied in one character at a time.

In order to add data to or modify data in your string, standard operations are provided.  For instance, you'll find AppendChar(), InsertAt(), RemoveAt(), and SetAt() methods as well as a Length property.  MakeReadOnly() and IsReadOnly() allow you to lock down the SecureString.  Clear(), Dispose(), and the finalizer take care of removing any trace of the SecureString from memory.

Here's a sample method that will read a SecureString out of the console.  Since the ConsoleKey objects have no way to be cleared, there's still the possibility that an attacker could get at this string, either by finding the ConsoleKey's sitting on the stack, having access to the stream behind Console.In before it gets overwritten.  However, reading into a SecureString helps to minimize the overall attack surface area.  Whenever possible, as you're reading data into the secure string, you'll want to make sure that you erase the character that you just added.

/// <summary>
/// Read a password from the console into a SecureString
/// </summary>
/// <returns>Password stored in a secure string</returns>
public static SecureString GetPassword()
{
    SecureString password = new SecureString();
    
    // get the first character of the password
    ConsoleKeyInfo nextKey = Console.ReadKey(true);

    while(nextKey.Key != ConsoleKey.Enter)
    {
        if(nextKey.Key == ConsoleKey.BackSpace)
        {
            if(password.Length > 0)
            {
                password.RemoveAt(password.Length - 1);

                // erase the last * as well
                Console.Write(nextKey.KeyChar);
                Console.Write(" ");
                Console.Write(nextKey.KeyChar);
            }
        }
        else
        {
            password.AppendChar(nextKey.KeyChar);
            Console.Write("*");
        }

        nextKey = Console.ReadKey(true);
    }    

    Console.WriteLine();

    // lock the password down
    password.MakeReadOnly();
    return password
}

Getting data out of the SecureString can be done with the help of the marshaller.  The Marshal class has been extended to provide methods that convert a SecureString into a BSTR or a raw block of ANSI or Unicode memory.  Once you're done using the unprotected string, you need to make sure to erase that copy.  This can be done with some additional methods added to the Marshal class.  For instance, a correct usage pattern would be:

IntPtr bstr = Marshal.SecureStringToBSTR(password);
    
try
{
    // ...
    // use the bstr
    // ...
}
finally
{
    Marshal.ZeroFreeBSTR(bstr);
}

Updated 11/26/04: Fixed a bug in GetPassword