I was curious that the Rfc2898DeriveBytes class Rfc2898DeriveBytes not support SecureString overloading to pass the password used in key derivation.
WPF allows you to process passwords as SecureString objects using PasswordBox . It seemed like such a loss, that the added security that this control offers was lost due to the fact that we could not pass the SecureString construct to the constructor. However, erickson has shown an excellent point of using byte[] instead of overloading string , since it is relatively easy to manage the contents of byte[] in memory than string .
Using erickson's suggestion as inspiration, I came up with the following shell, which should allow you to use a SecureString protected password value with a minimal exposure of the plaintext value in memory.
private byte[] DeriveKey(SecureString password, byte[] salt, int iterations, int keyByteLength) { IntPtr ptr = Marshal.SecureStringToBSTR(password); byte[] passwordByteArray = null; try { int length = Marshal.ReadInt32(ptr, -4); passwordByteArray = new byte[length]; GCHandle handle = GCHandle.Alloc(passwordByteArray, GCHandleType.Pinned); try { for (int i = 0; i < length; i++) { passwordByteArray[i] = Marshal.ReadByte(ptr, i); } using (var rfc2898 = new Rfc2898DeriveBytes(passwordByteArray, salt, iterations)) { return rfc2898.GetBytes(keyByteLength); } } finally { Array.Clear(passwordByteArray, 0, passwordByteArray.Length); handle.Free(); } } finally { Marshal.ZeroFreeBSTR(ptr); } }
This approach takes advantage of the fact that BSTR is a pointer pointing to the first character of a data string with a four-byte prefix.
Important points:
- By wrapping
Rfc2898DeriveBytes in a using statement, it ensures that it is deterministic. This is important because it has an internal HMACSHA1 object, which is KeyedHashAlgorithm , and needs to have a copy of the key (password) that it has in order to be reset to zero in the Dispose call. See the help source for more information. - As soon as we finish with
BSTR , we zero it and release it through ZeroFreeBSTR . - Finally, we clear (clear) our copy of the password.
- Update: Added
byte[] pinning. As discussed in the comments of this answer, if byte[] not fixed, the garbage collector can move the object during the collection, and we would have no way to reset the original copy.
This should keep the plaintext password in memory for the shortest possible time and not weaken the benefits of using SecureString too much. Although, if an attacker has access to RAM, you probably have more problems. Another point is that we can only manage our own copies of the password, the API that we use can very poorly sort (not reset / clear) our copies. As far as I know, this does not apply to Rfc2898DeriveBytes , although their copy of the byte[] key (password) is not fixed and, therefore, traces of the array may hang if they were moved to the heap before resetting to zero. The message here is that the code may look safe, but problems may lie beneath it.
If anyone finds any serious flaws in this implementation, let me know.