Wednesday, August 31, 2011

Protecting Passwords from Forensic Detection with SecureString

Today we will be taking a look at the SecureString class and ways to use it to provide more security to our applications in regards to dealing with confidential login information. I say "more" security because as most you reading this already probably know, a determined, knowledgeable attacker would still be able to find a way to retrieve confidential information if they are so inclined to do so. Now that being said, taking steps to properly secure your applications will make it far more difficult for them to succeed and may just deter an attacker to go and find an easier target.


So lets start out by looking at how your application accepts a users password.


  • String (The Worst Way)


    By far the most common form of user input and in as far as dealing with passwords, the most dangerous. The main reason, strings are immutable object and their contents are stored as plain text. Once created we have no way of clearing out their contents or controlling when they are reclaimed by the garbage collector. The garbage collector itself makes matters worse in that it moves data around internally which may result in multiple copies being made of your confidential data.


  • Character Arrays (A Better Option)


    An alternative to using strings is to use a character array to accept a users password. The biggest advantage character arrays have over strings is that the elements of the array can be zeroed out after they are used to confirm a users identity. A draw back that character arrays share with strings is that that also store their contents as plain text. With a little extra work this concern can be addressed through use of the ProtectedData and ProtectedMemory classes found in the System.Security.Cryptography namespace.

  • SecureString (What this Whole Page is About)


    The SecureString class was created as a way of created as a way to safely retain text data in managed memory. As text is added to the SecureString, it is automatically encrypted. Internally the SecureString pins the data so that it can’t be moved around by the garbage collector, thus ensuring you don’t have multiple copies of confidential information floating around in memory. Another security feature of SecureString is that, with the exception of the Length property, there are no members to inspect, compare or convert the SecureString value.


Now you might be thinking, "Why would I use this if I can’t get the password value back to check it?" Well there are ways of retrieving the contents of a SecureString using several methods of the Marshal class. By looking at the MSDN documentation, you will see that there are five methods (the names of which all start with "SecureStringTo[unmanaged string type name]") that deal with retrieving the value of a SecureString as an unmanaged string type. Why deal with unmanaged strings? Well as was pointed out before; one of the biggest problems of keeping confidential data secure is that it hangs around in memory waiting for the garbage collector to reclaim it. Unmanaged code resides outside of the reach of the garbage collector so doesn’t have this problem. We just have to make sure we clean up after ourselves. The Marshal class makes this easy in that every "SecureStringTo" method has a corresponding "ZeroFree" method, which nulls out all allocated memory before freeing the memory location.


An Example


Well thats enough explination, lets get to an actual example.

-- Important -- Three important things before getting into the example.
  1. Internally, the SecureString relies on Windows Data Protection (DPAPI) for encryption/decryption so SecureStrings can only be used on Windows 2000 SP1 systems or higher.

  2. If running under a partial trust environment, you will need to have Unmanaged code access privileges as all methods of the Marshal class perform a LinkDemand for these privileges.

  3. For brevity, I’m leaving out exception checking but many of the calls to unmanaged code are prone to generating exception so in a real implementation always include proper exception handling.

Two things I've noticed when looking for SecureString examples online; most examples are done as console applications (when most likely you will be writing a Winforms or WPF application) and that most people somewhere along the line make the mistake of converting the password back into a string, thus defeating the whole purpose of using the SecureString in the first place. For this example we will avoid both of those situations.


You can download the source code for this project here.



To start off we will be creating a new windows forms application to check against a hard coded password (in this case, the word “PASSWORD”). First off we will define a user interface such as the one seen here. I won’t go into detail about the form as you can download the full source code for the project or just create your own. The important thing to note about this form is that we are NOT using a Textbox control to get the users input. Instead we are using a hosted WPF PasswordBox control. The reason for this choice is that Textbox control stores and reveals data as Strings, thus defeating the entire point of this example. The WPF PasswordBox control on the other hand uses a SecureString to hold user input and provides an easy way of getting our SecureString object.


I wanted to illustrate multiple ways of verifying a user password, so our user entered password is going to be tested against a VB BSTR formatted string, a C-style LPSTR formatted ANSI string and a hash value of the password. To begin, we will be defining the three password values we will be testing against (again, we would NEVER do this in a real application). Add the following lines at the beginning of the class definition.


// The Values to check against
private IntPtr _bstr;
private IntPtr _ansi;
private byte[] _hash;

To get the BSTR and ANSI string values, we will just user the Marshal classes StringToBSTR() and StringToHGlobalAnsi() methods.


_bstr = Marshal.StringToBSTR("PASSWORD");
_ansi = Marshal.StringToHGlobalAnsi("PASSWORD");

Constructing the hash value is a little more involved as we first have to convert the password value into a byte array, which will then be passed to the hashing algorithm. For this example I’ll be using a SHA256 algorithm but feel free to use whatever hashing algorithm you want.


SHA256 sha = new SHA256Managed();

byte[] raw = new byte[8];
int index = 0;
foreach (char c in "PASSWORD".ToCharArray())
{
raw[index] = Convert.ToByte(c);
index++;
}

_hash = sha.ComputeHash(raw);

// clean up
for(int i = 0; i < raw.Length; i++)
raw[i] = 0;
raw = null;

We will use the radio button controls on the form to indicate what type of test we should perform. In my example, I chose to have the radio buttons assign a new click event handler to the check button as they are changed. You can handle then in whatever way you want. I’ll leave that implementation detail to you so we can so we can get started on the actual checks themselves.


Check against an ANSI LPSTR string



The simplest example will be to work with the ANSI LPSTR strings. The image to the side shows how LPSTR’s are represented in memory. They are basically an array of single byte characters with a terminating null. Since these are null terminated strings, we don’t know in advance what the length is (at least we wouldn’t if we hadn’t hard coded it). Our strategy in this case is to perform a byte by byte comparison until one of the following occurs.


  1. We get a mismatch (fail).

  2. We encounter only one terminating null indicating the passwords are not the same length and as such cannot match (fail).

  3. We encounter two matching terminating nulls indicating passwords are the same length and do match (password verified).


We start by getting our user entered password value as an ANSI string.


ansi = Marshal.SecureStringToGlobalAllocAnsi(pb1.passwordBox1.SecurePassword);

Now we will perform our byte by byte comparison until one of the three conditions occurs.



int offset = 0;
bool end = false;
do
{
byte b1 = Marshal.ReadByte(_ansi, offset);
byte b2 = Marshal.ReadByte(ansi, offset);

offset++;

if (b1 == b2)
{
match = true;
// check for nulls
if (b1 == 0x0 && b2 == 0x0)
end = true;
}
else
{
end = true;
match = false;
}

} while (!end);

It is important to clear out the resources once we are finished with them so we will encapsulate our test in a try/finally block to ensure that the Marshal.ZeroFreeGlobalAllocAnsi() method is always called.



finally
{
// Make sure the ansi string is always zeroed out and released.
if (ansi != IntPtr.Zero)
Marshal.ZeroFreeGlobalAllocAnsi(ansi);
}

Here is the Checking code in its entirety.



private void CheckANSI()
{
bool match = false;
IntPtr ansi = IntPtr.Zero;

try
{
//Convert the SecureString to an unmanaged ANSI string.
ansi = Marshal.SecureStringToGlobalAllocAnsi(pb1.passwordBox1.SecurePassword);

int offset = 0;
bool end = false;
do
{
byte b1 = Marshal.ReadByte(_ansi, offset);
byte b2 = Marshal.ReadByte(ansi, offset);

offset++;

if (b1 == b2)
{
match = true;
// check for nulls
if (b1 == 0x0 && b2 == 0x0)
end = true;
}
else
{
end = true;
match = false;
}
} while (!end);
}
finally
{
// Make sure the ansi string is always zeroed out and released.
if (ansi != IntPtr.Zero)
Marshal.ZeroFreeGlobalAllocAnsi(ansi);
}
PasswordMatch(match);
}

The PasswordMatch() method is just a simple method to display a messagebox showing if the passwords match or not.

Check Against a BSTR String



Working with BSTR strings is a little different. The image to the side shows how BSTR strings are represented in memory. The important things to remember when dealing with BSTR strings are that we are now dealing with 16-bit Unicode characters preceded by a 4 byte length value. Another important thing to know is that the IntPtr memory location for this string, points to the first character element and not to the length value. The strategy we will use to check against BSTR strings is as follows:
Get the length of both passwords. If the same lengths, compare the two values in 2 byte blocks (ints) until the full length of the passwords have been read. If there are no mismatches, then the user password is valid. We start by converting out SecureString value into a BSTR string.


// Convert the secureString to an unmanaged BSTR.
bstr = Marshal.SecureStringToBSTR(pb1.passwordBox1.SecurePassword);

Now in order to get the BSTR lengths, We need to go back 4 bytes from the memory address refered to in the IntPtr variables. Lets go an define a constant just under our class definition to represent this offset.


private const int BSTR_LENGTH_OFFSET = -4;

Now lets get the lengths of our BSTR strings like so:


int p1Len = Marshal.ReadInt32(_bstr, BSTR_LENGTH_OFFSET);
int p2Len = Marshal.ReadInt32(bstr, BSTR_LENGTH_OFFSET);

We will now check both lengths to see if they are equal and if they are do a character by character comparison using a for loop. Since we are dealing with 2 byte characters, we will increment our loop counter by 2 each cycle.


if (p1Len == p2Len)
{
// if the two lengths match, then do a short by short comparison (remember BSTR uses
// Unicode characters).

bool charCompare = true;
for (int i = 0; i < p2Len; i += 2)
{
if (Marshal.ReadInt16(_bstr, i) != Marshal.ReadInt16(bstr, i))
{
charCompare = false;
break;
}
}
match = charCompare;
}
else
match = false;

Just like with the LPSTR method, we need to insure that everything gets cleaned up so enclose the check in a try/finally block and call the Marshal.ZeroFreeBSTR() method.


finally
{
// Make sure the BSTR string is always zeroed out and released.
if (bstr != IntPtr.Zero)
Marshal.ZeroFreeBSTR(bstr);
}

The full code for this check method is here:



private void CheckBSTR()
{
bool match = false;
IntPtr bstr = IntPtr.Zero;
try
{
bstr = Marshal.SecureStringToBSTR(pb1.passwordBox1.SecurePassword);

int p1Len = Marshal.ReadInt32(_bstr, BSTR_LENGTH_OFFSET);
int p2Len = Marshal.ReadInt32(bstr, BSTR_LENGTH_OFFSET);

if (p1Len == p2Len)
{
bool charCompare = true;
for (int i = 0; i < p2Len; i += 2)
{
if (Marshal.ReadInt16(_bstr, i) != Marshal.ReadInt16(bstr, i))
{
charCompare = false;
break;
}
}
match = charCompare;
}
else
match = false;
}
finally
{
if (bstr != IntPtr.Zero)
Marshal.ZeroFreeBSTR(bstr);
}
PasswordMatch(match);
}

Checking Passwords by Hash Value


Sometimes you may not be checking passwords directly against each other. Sometimes for an extra layer of security, a system may only store the hash value of a users password. If this is the case, you will need to do a hash to hash comparison using the same hashing algorithm. That is what our final example will be doing against the hash we created earlier. We will start by instantiating the hasher algorithm class (in this we will be using SHA256) and defining a byte array to hold the hash value. You will also notice we are creating a GCHandle structure and associating it with the byte array for the hash. I'll get to that in a minute.


SHA256 hasher = new SHA256Managed();
byte[] theHash = null;
GCHandle hashHandle = GCHandle.Alloc(theHash, GCHandleType.Pinned);

The next thing we need to do is get the value of our SecureString to create a hash from it. We will also need to get the length of the password, which we will use in just a bit.


-- Important -- At this point, it is important to have some knowledge as to how the original hash value was derived. In this example, the hash value we will be checking against was created using a byte array representing the equivalent of a single byte per character string so we will be retrieving the SecureString with the Marshal.SecureStringToGlobalAllocAnsi() method. Had the original hash been created from a 2 byte per character string, we would need to retrieve the value of our SecureString we would need to retrieve the value in the same way or our hashes would never match.

int pwLength = pb1.passwordBox1.SecurePassword.Length;
unmanagedStr = Marshal.SecureStringToGlobalAllocAnsi(pb1.passwordBox1.SecurePassword);

This next step represent the weak link in this operation in that we need to bring the contents of our password into managed code (albeit as a byte array and not as a string). This is an unavoidable step in that we need that data to create a hash, but at least we can take precautions to minimize our risk as quickly as possible.



byte[] pw = new byte[pwLength];
GCHandle pwHandle = GCHandle.Alloc(pw, GCHandleType.Pinned);
try
{
Marshal.Copy(unmanagedStr, pw, 0, pwLength);

// make the hash and zero out the byte array.
theHash = hasher.ComputeHash(pw);
}
finally
{
for (int i = 0; i < pw.Length; i++)
pw[i] = 0;
if (pwHandle.IsAllocated)
pwHandle.Free();
hasher = null;
}

In the preceding block of code, we declare a byte array of the proper size, bring the contents of the password into that array using the Marshal.Copy() method, create our hash value and immediately zero out the contents of that array. Once again you will notice we have created a GCHandle structure and associated it with our byte array. Let me explain its purpose. The primary purpose of the GCHandle structure is to allow unmanaged code, like P/Invoke calls or COM objects, to access managed objects. It can also be used to pin an object in memory thus preventing the garbage collector from copying it or moving it. That is how we are using it right now. We are insuring that the byte array containing the password data will not be copied or moved until we explicitly free it. It is important to always free any pinned objects as soon as possible as they hurt the efficiency of the garbage collector and can produce memory leaks if forgotten.


Now that we have the hash value for the user entered password, we can perform a byte wise compare to validate if the password is correct.



bool hashequal = true;
for (int i = 0; i < _hash[i]; i++)
{
if (_hash[i] != theHash[i])
{
hashequal = false;
break;
}
}
match = hashequal;

As with the other methods, we perform all these steps inside of a try/finally block to ensure your unmanaged string is zeroed out. Additionally in this block we zero out the calculated hash value and free its pinned GCHandle.



finally
{
// Make sure the unmananged string is always zeroed out and released.
if (unmanagedStr != IntPtr.Zero)
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedStr);
if (theHash != null)
for (int i = 0; i < theHash.Length; i++)
theHash[i] = 0;
if (hashHandle.IsAllocated)
hashHandle.Free();
}

The full method code is right here



private void CheckHash(object sender, EventArgs e)
{
bool match = false;
IntPtr unmanagedStr = IntPtr.Zero;

SHA256 hasher = new SHA256Managed();
byte[] theHash = null;
GCHandle hashHandle = GCHandle.Alloc(theHash, GCHandleType.Pinned);

try
{
// Need to get an unmanaged reference to the contents of the SecureString.
int pwLength = pb1.passwordBox1.SecurePassword.Length;
unmanagedStr = Marshal.SecureStringToGlobalAllocAnsi(pb1.passwordBox1.SecurePassword);

// We now need to create a byte array of the password in order to compute a SHA hash of for it.
// --NOTE-- This is the weak point of using this method as we are bringing the contents of our
// password into managed code. We will try to minimize any exposure risk by immediatly zeroing
// out the array after the hash is created.

byte[] pw = new byte[pwLength];
GCHandle pwHandle = GCHandle.Alloc(pw, GCHandleType.Pinned);
try
{
Marshal.Copy(unmanagedStr, pw, 0, pwLength);

// make the hash and zero out the byte array.
theHash = hasher.ComputeHash(pw);
}
finally
{
for (int i = 0; i < pw.Length; i++)
pw[i] = 0;
if (pwHandle.IsAllocated)
pwHandle.Free();
hasher = null;
}

// now do a hash comparision.

bool hashequal = true;
for (int i = 0; i < _hash[i]; i++)
{
if (_hash[i] != theHash[i])
{
hashequal = false;
break;
}
}
match = hashequal;
}
finally
{
// Make sure the unmananged string is always zeroed out and released.
if (unmanagedStr != IntPtr.Zero)
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedStr);
if (theHash != null)
for (int i = 0; i < theHash.Length; i++)
theHash[i] = 0;
if (hashHandle.IsAllocated)
hashHandle.Free();
}

PasswordMatch(match);
}

And that concludes our little example of safely using a SecureString to validate a users password without exposing it. I hope this was helpful to anyone.

No comments:

Post a Comment