Unfortunately, without more details, it would be difficult to know what the best answer is. One particular detail that's missing here is where the SecureString
object comes from. Do you create it for the purpose of performing this hash? Or is the password already represented by the SecureString
object, which you are passing to other APIs?
If the former, then it suggests that you already have an unencrypted, non-deterministic-lifetime string in your process containing the password. If the latter, then while the lifetime of the unencrypted version(s) of the password may be deterministic, note that the password still winds up decrypted at various points of execution.
That said, in terms of your specific questions:
How to work out the number of bytes allocated by SecureStringToGlobalAllocUnicode
It seems to me that you should be able to trust that doubling the original text's length would be reliable. The SecureString.Length
property returns the number of char
objects composing the string, i.e. the number of 16-bit UTF16 values, so the bytes are just twice that. The Length
property isn't taking into account Unicode code points that take two 16-bit values (i.e. low and high surrogate), so it should be accurate for byte-length computations.
That said, if you don't trust that…the allocated string should be null terminated, so you can just do a normal scan of the string. Note that if you use the BSTR method for the string, the string is prefixed with a 32-bit byte count (not character count) representing the string, not counting its null terminator; you can retrieve that by subtracting 4 from the IntPtr
returned, getting the four bytes there, and converting that back to an int
value.
The appropriate function to use when I need n bytes after an IntPtr and don't want to allocate a managed byte[] and use Marshal.Copy
There are lots of ways to do this. I think one of the simpler approaches is to p/invoke the Windows CopyMemory()
function:
[DllImport("kernel32.dll")]
unsafe extern static void CopyMemory(void* destination, void* source, IntPtr size_t);
Just pass the appropriate IntPtr
values to the method, using either the IntPtr.ToPointer()
method or the explicit conversion to void*
that's available. Used like this:
unsafe
{
CopyMemory(IntPtr.Add(unsafeBuffer, usernamePart.Length).ToPointer(),
unmanagedPwd.ToPointer(), new IntPtr(lenPasswordArray));
}
In .NET 4.6 (according to MSDN...I haven't used this myself...still stuck on 4.5), you can (will be able to) use the Buffer.MemoryCopy()
method. E.g.:
Buffer.MemoryCopy(unmanagedPwd.ToPointer(),
IntPtr.Add(unsafeBuffer, usernamePart.Length).ToPointer(),
lenPasswordArray,
lenPasswordArray);
(Note that I think you had a type in your original example; you are adding lenPasswordArray
to the unsafeBuffer
pointer to determine the location to which to copy the password data. I've corrected that in the above examples, using the user name length instead, since you seem to be wanting to copy the password data immediately after the data for the user name which has already been copied).
How to encrypt those bytes
What do you mean by that? Are you asking how to hash the bytes? I.e. run the MD5 hash algorithm on them? Note that that's not encryption; there's no practical way to decrypt the value (MD5 security flaws notwithstanding).
If you simply mean to hash the bytes, you would need an MD5 implementation that could operate on unmanaged memory. I'm not sure whether Windows has an unmanaged MD5 API, but it does have cryptography in general. So you could p/invoke to access those functions. See Cryptographic Service Providers for more details.
I will note that at this point, you now have the unencrypted data in memory, in two different places: the originally decrypted memory block from the call to SecureStringToGlobalAllocUnicode()
, and of course the new copy you made copying to the unsafeBuffer
. You can control the lifetime of these buffers more closely than you can a System.String
object, but other than that you have the same risk during that lifetime of malicious code inspecting your process and recovering the plaintext.
If you mean something other than hashing, please be more specific about how and why you want to "encrypt those bytes".
How to reliably zero out and free anything I've allocated (I'm very new to unsafe code)
I don't know what unsafe
has to do with the question. Indeed, except for the places where you need to use void*
, your code example itself doesn't need unsafe
.
As for zeroing out the memory buffers, the code you have seems to be fine to me. If you want something slightly more efficient than allocating a whole new byte[]
buffer just for the purpose of setting another memory location to all zeroes, you can p/invoke the SecureZeroMemory()
Windows function instead (similar to the CopyMemory()
example above).
Now, all of the above said, as I mentioned in the comments, it seems to me that there are ways to do this in managed, safe code, simply by controlling the lifetime of the intermediate objects explicitly yourself. For example:
static string SecureComputeHash(string username, SecureString password)
{
byte[] textBytes = null;
IntPtr textChars = IntPtr.Zero;
try
{
byte[] userNameBytes = Encoding.Unicode.GetBytes(username);
textChars = Marshal.SecureStringToGlobalAllocUnicode(password);
int passwordByteLength = password.Length * 2;
textBytes = new byte[userNameBytes.Length + passwordByteLength];
userNameBytes.CopyTo(textBytes, 0);
Marshal.Copy(textChars, textBytes, userNameBytes.Length, passwordByteLength);
using (MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider())
{
return Convert.ToBase64String(provider.ComputeHash(textBytes));
}
}
finally
{
// Clean up temporary buffers
if (textChars != IntPtr.Zero)
{
Marshal.ZeroFreeGlobalAllocUnicode(textChars);
}
if (textBytes != null)
{
for (int i = 0; i < textBytes.Length; i++)
{
textBytes[i] = 0;
}
}
}
}
(I used base64 encoding to convert your hashed byte[]
result to a string. The simple call to ToString()
you showed in your example won't do anything useful, as it just returns the type name for a byte[]
object. I think base64 is the most efficient, useful way to store the hashed data, but you can of course use any representation you find useful).
The above assumes that your password is already in a SecureString
object. Of course, if you are simply initializing a SecureString
object from some other non-encrypted object, you could do the above differently, such as creating a char[]
directly from the non-encrypted object (which could be e.g. string
or StringBuilder
).
I don't see how your unmanaged approach would be noticeably better than the above.
The only exception I can see is if you are worried that the MD5CryptoServiceProvider
class might leave some copy of your data in its own internal data structures. That could be a valid concern, but then you would also have that concern for your unmanaged approach too, since you haven't shown what MD5 implementation you would actually use there (you would have to make sure whatever implementation you use is careful about not leaving copies of your data).
Personally, I suspect (but don't know for sure) that given the word "crypto" in the MD5CryptoServiceProvider
class name, that class is careful to clear temporary in-memory buffers.
Other than that possible concern, the entirely managed approach accomplishes the same thing, with IMHO less fuss.