0

I'm trying to persist whether username and password combination were valid last time a program executed, but without storing the username and password themselves. The goal isn't validation, just to prevent needless attempts to use invalid credentials that could get a user locked out of a service (in this case, SharePoint, but that's not pertinent here).

My approach is to concatenate the username and password and take an MD5 hash (it's fast, and it'll validate against a provided username/password combination).

This turns out to require a bunch of stuff I don't know. Please see below for my current (not working) approach, and if anyone can provide guidance as to what I should be doing, it would be very useful.

unsafe
{
    byte[] usernamePart = Encoding.Unicode.GetBytes(this.Username);
    IntPtr unmanagedPwd = IntPtr.Zero;
    unmanagedPwd = Marshal.SecureStringToGlobalAllocUnicode(this.Password);

    // Question 1: How many bytes do I need to copy?
    int lenPasswordArray = somemethod(this.Password);
    IntPtr unsafeBuffer = Marshal.AllocHGlobal(usernamePart.Length + lenPasswordArray);
    Marshal.Copy(usernamePart, 0, unsafeBuffer, usernamePart.Length);

    // Question 2: Marshal.Copy takes a byte[]; I have an IntPtr. How to copy after the username
    Marshal.Copy(unmanagedPwd, 0, IntPtr.Add(unsafeBuffer, lenPasswordArray), lenPasswordArray);

    var provider = new System.Security.Cryptography.MD5CryptoServiceProvider();
    //Question 3: I now have an IntPtr with username and password together. But 
    // provider takes a byte[]... I don't want to convert to byte[], because it'll end up
    // with the same System.String problem
    var targetHash = provider.ComputeHash(unsafeBuffer);

    // Question 4: How do I clean up safely?
    Marshal.ZeroFreeGlobalAllocUnicode(unmanagedPwd);
    Marshal.Copy(new byte[usernamePart.Length + lenPasswordArray], 0, unsafeBuffer, usernamePart.Length + lenPasswordArray);
    Marshal.FreeHGlobal(unsafeBuffer);
}

As in the comments, there's 4 things I need to know:

  • How to work out the number of bytes allocated by SecureStringToGlobalAllocUnicode
  • 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
  • How to encrypt those bytes
  • How to reliably zero out and free anything I've allocated (I'm very new to unsafe code)

Edit: For clarity, what I want is the secure version of:

byte[] usernamePart = Encoding.Unicode.GetBytes(this.Username);
byte[] passwordPart = Encoding.Unicode.GetBytes(this.Password.ConvertToUnsecureString());
byte[] all = usernamePart.Concat(passwordPart).ToArray();
var provider = new System.Security.Cryptography.MD5CryptoServiceProvider();
return provider.ComputeHash(all).ToString();
tobriand
  • 1,095
  • 15
  • 29
  • Forgive my ignorance, why are you doing this in an `unsafe` context? – NWard Apr 13 '15 at 15:30
  • 1
    Why are you using unsafe code in the first place? Seems like this could all be done with safe types (`string`, `byte[]`) – D Stanley Apr 13 '15 at 15:30
  • 1
    Copying the secure string to a `byte[]` throws away the whole "security" of `SecureString`. You'll probably have to use some unmanaged library to do the MD5. – Luaan Apr 13 '15 at 15:31
  • Do I actually copy the this.Password to a byte[]? I do for this.Username (but that's just a system.string), but as far as I can tell, password gets passed through Marshal... – tobriand Apr 13 '15 at 15:32
  • I am skeptical of the value of `SecureString` in this scenario. _Some_ code in your process is going to need to access the unencrypted version of your string (which you are producing here with your call to `SecureStringToGlobalAllocUnicode()`) . It seems to me that the only added value is managing the lifetime of the string in memory; you could accomplish the same using `StringBuilder` (which you can overwrite, unlike a `String` instance) and its `CopyTo()` method, which can provide the `char[]` for getting bytes or the hash. Just overwrite the `char[]` and `byte[]` objects when you're done. – Peter Duniho Apr 13 '15 at 17:16
  • I was under the impression that that's pretty much the *only* benefit of `SecureString` - i.e. sooner or later code needs to compare an actual password (unless you're storing password hashes only, in which case, same difference - even if the word itself isn't compromised access can be if the hash leaks), at which point it needs to come out of secure string. Also, if `StringBuilder` sooner or later returns a `byte[]` or `char[]`, won't they leak? Both `byte` and `char` are immutable (I think - aren't they?), and are managed, which is exactly what causes the problem with String... – tobriand Apr 14 '15 at 08:35
  • @tobriand: typically, one passes a `SecureString` object to framework code (e.g. calling the `Process.Start()` overload that takes a password, as in the example found on MSDN). And yes, at that point the string is decrypted and used. By "only added value", my point is that in other scenarios one can pass the object to some other method that will use it; here, there is no other such method. The OP winds up having to manage the data explicitly himself. As far as immutability of `byte` or `char`, it's the `byte[]` and `char[]` array that are overwritten, and those are _not_ immutable. – Peter Duniho Apr 14 '15 at 16:54
  • @tobriand: to be clear: the problem with `System.String` isn't the immutability of the underlying buffer (which is in fact not actually immutable), but the immutability of the object itself. I.e. it won't let you modify the underlying buffer, even though it _could_ do so. The `SecureString` object gives you control over the lifetime of that buffer (and encrypts it to boot), while `System.String` does not. – Peter Duniho Apr 14 '15 at 16:56

1 Answers1

1

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.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136