Be Lazy, Be Simple, SimpleMembershipProvider

Hashing passwords with SimpleMembershipProvider

If you do a Google search on laziness and software developers you will find a number of decent articles from highly accredited people who have associated great developers with being lazy such as the classic from Jeff Atwood and many others.  If you think about some possible reasons behind automation scripts, DRY principle and the like, one could make a valid point.  But that isn’t the laziness I am really referring to.  Being lazy by utilizing already built, tried and tested libraries such as the membership provider in ASP.NET, SimpleMembershipProvider.

Sometimes there is great advantage or valid reason for reinventing the wheel and opting to write some similar application that has already been exhausted more times than you can count.  But, sometimes we can get ourselves into trouble attempting to reinvent an already complex and highly sophisticated whatcha-call-it that does a do-hicky-mabob.  One of those controversial topics is in the area of cryptography.  This is an area where the tried and tested really have an advantage.  Before assuming this is a blog on the SimpleMembershipProvider bandwagon, read on.

If you read my last blog on implementing PBKDF2 in .NET you now should have a good understanding how to implement it, what components are required and some additional points to keep in mind when implementing.  Just to point out, in that article we aren’t attempting to roll our own cryptography algorithm, but implementing the available, tried an tested PBKDF2 function.

That being said, back when ASP.NET MVC 4 and ASP.NET 4.5 was released, they included some new membership related features that might be exactly what you’re looking for.  The SimpleMembershipProvider is the latest in membership providers that Microsoft has released that attempts to simplify membership requirements.  Jon Galloway has a robust article outlining the historical progression of membership providers from the ASP.NET Membership system in 2005 until now and details around problems each attempted to solve.  His article is well worth the read as well as providing additional details on the features in the SimpleMembershipProvider.

The intention of this post is not about how to implement and utilizing the SimpleMembershipProvider, but rather analyzing some of the underpinnings of the authentication used.   It ties in nicely with the last few posts on User Account Security and implementing the PBKDF2 in .NET.  I have seen more often than not, the everyday developer running full speed ahead, blindly relying on that third-party library or plugin that claims to be providing the security you need, without once giving it any consideration to validate it’s claims.  I will be the first admit that I had been one of those developers at one point or another.

If you are looking for that tutorial on implementing the  SimpleMembershipProvider, there is no shortage of decent articles to get you on the right foot.  I would highly recommend reading the already mentioned Jon Galloway post first to get some background.

The quickest way to get underway with understanding and utilizing the SimpleMembershipProvider in your next application is by getting into the code.  Unfortunately, out of the gate, only the internet template found by selecting the ASP.NET MVC Web Application project  (not empty) provides and uses the SimpleMembershipProvider.   So you can easily get started by creating a new Project in Visual Studio 2013

New ASP.NET MVC Project Template

 

 

Digging into SimpleMembershipProvider

Upon creation of the project, you should see there is an AccountController located in the Controllers folder

Account Controller in Project

 

Where we want to concentrate is the Register method in this controller which comprises of the two referenced methods from a static class WebSecurity that does all the heavy lifting that we want to look at CreateUserAccount and Login.

[csharp]</pre>
<pre>public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}

// If we got this far, something failed, redisplay form
return View(model);
}

<span style="color: #444444;">[/csharp]

The WebSecurity class is provided via the WebMatrix.WebData namespace.  In order to dig into what exactly is going on under the covers of these you will need pull down the open source project aspnetwebstack from CodePlex or you can just follow along here.

In the SimpleMemberShipProvider class you will see the CreateUserAndAccount method which I am not going to go over line by line, but gets us another step closer


[csharp]</pre>
<pre>// Inherited from ExtendedMembershipProvider ==> Simple Membership MUST be enabled to use this method
public override string CreateUserAndAccount(string userName, string password, bool requireConfirmation, IDictionary<string, object> values)
{
VerifyInitialized();

using (var db = ConnectToDatabase())
{
CreateUserRow(db, userName, values);
return CreateAccount(userName, password, requireConfirmation);
}
}</pre>
<pre>[/csharp]


From here, we will want to jump into the CreateAccount method they are returning from

[csharp]

// Inherited from ExtendedMembershipProvider ==> Simple Membership MUST be enabled to use this method
public override string CreateAccount(string userName, string password, bool requireConfirmationToken)
{
VerifyInitialized();

if (password.IsEmpty())
{
throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword);
}

string hashedPassword = Crypto.HashPassword(password);

//removed for brevity

[/csharp]

Now we are getting somewhere.  I have not included the entire method, as the Crypto.HashPassword(password) is what I wanted to focus in on.  So lets jump into what is going on here.

[csharp]

public static string HashPassword(string password)
{
if (password == null)
{
throw new ArgumentNullException("password");
}

// Produce a version 0 (see comment above) password hash.
byte[] salt;
byte[] subkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, SaltSize, PBKDF2IterCount))
{
salt = deriveBytes.Salt;
subkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}

byte[] outputBytes = new byte[1 + SaltSize + PBKDF2SubkeyLength];
Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);
Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, PBKDF2SubkeyLength);
return Convert.ToBase64String(outputBytes);
}

[/csharp]

We can see that after some initial defensive programming, they are indeed using the Rfc2898DerivedBytes class.  In this situation they are allowing the function to derive the salt itself by only providing the size of the salt they want generated.

The constants they are utilizing in the class are as follows

[csharp]

private const int PBKDF2IterCount = 1000; // default for Rfc2898DeriveBytes
private const int PBKDF2SubkeyLength = 256 / 8; // 256 bits
private const int SaltSize = 128 / 8; // 128 bits

[/csharp]

So they are using a 16 byte size salt key and a 32 byte size derived key for the hash value and finally iteration count of 1000.  An iteration count of 1000?  Hopefully that caught your eye as well.  In the RFC 2898 released back in 2000 (which is interesting and a lengthy read) suggested at the time that 1000 was a modest number.

Moving on, we can see that they concatenate the data into a single byte array with the leading single byte for housing the version.  Currently, I am of the opinion (read: not entirely sure) that the versioning is encapsulating all hashing ingredients such as the hash algorithm (HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations) as opposed to one particular ingredient such as the iteration count or other.

The next WebSecurity class method we mentioned we wanted to look at is the Login method, so lets take a peak at that.

[alert type=”info”]Note: for the sake of making you transverse a number of methods, I am jumping straight to the point in the Login process to where they are validating the submitted password guess against the actual correct password.[/alert]

In the process we come to the CheckPassword method in the SimpleMembershipProvider class

[csharp]

private bool CheckPassword(IDatabase db, int userId, string password)
{
string hashedPassword = GetHashedPassword(db, userId);
bool verificationSucceeded = (hashedPassword != null && Crypto.VerifyHashedPassword(hashedPassword, password));

[/csharp]

We see they are passing the retrieved correct password hash as well as the submitted password guess (not hashed) to the Crypto class VerifyHashedPassword .

[csharp]

public static bool VerifyHashedPassword(string hashedPassword, string password)
{
if (hashedPassword == null)
{
throw new ArgumentNullException("hashedPassword");
}
if (password == null)
{
throw new ArgumentNullException("password");
}

byte[] hashedPasswordBytes = Convert.FromBase64String(hashedPassword);

// Verify a version 0 (see comment above) password hash.

if (hashedPasswordBytes.Length != (1 + SaltSize + PBKDF2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
{
// Wrong length or version header.
return false;
}

byte[] salt = new byte[SaltSize];
Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, SaltSize);
byte[] storedSubkey = new byte[PBKDF2SubkeyLength];
Buffer.BlockCopy(hashedPasswordBytes, 1 + SaltSize, storedSubkey, 0, PBKDF2SubkeyLength);

byte[] generatedSubkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, PBKDF2IterCount))
{
generatedSubkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
return ByteArraysEqual(storedSubkey, generatedSubkey);
}

[/csharp]

Again, after some defensive programming, they eventually deconstruct the hashedPassword argument to the salt and password (storedSubKey). This allows them to provide the necessary hashing ingredients with the provided password guess to the Rfc2898DeriveBytes constructor and generate a hash value of the password guess.  Finally, we see that once the password guess is hashed, it as well as the correct password hash is passed to the ByteArraysEqual method for comparison.

[csharp]

// Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized.
[MethodImpl(MethodImplOptions.NoOptimization)] private static bool ByteArraysEqual(byte[] a, byte[] b)
{
if (ReferenceEquals(a, b))
{
return true;
}

if (a == null || b == null || a.Length != b.Length)
{
return false;
}

bool areSame = true;
for (int i = 0; i < a.Length; i++)
{
areSame &= (a[i] == b[i]);
}
return areSame;
}

[/csharp]

We can see here starting out that they started by implementing the method optimization attribute MethodImpl setting the NoOptimization flag.  This directly affects the JIT compiler involvement and in this case the lack of optimization is helping against not leaking information to an attacker through means of analyzing the response times of failed response.  However, though this is in place, they do have the early termination if the byte arrays are not equal (however, this most likely will not be the case if hashed with the same ingredients, specifically: the derived key length of the hash value).

If there is no early termination they will eventually iterate through the submitted password guess hash and compare each byte with the correct password hash byte for byte to evaluate whether or not these two hashes are exactly the same or not, concluding whether or not the user has submitted a correct password.

In Conclusion

So we can now see that the SimpleMembershipProvider is utilizing the password-based key derivation function (PBKDF2) as its means of hashing password.  You are also now aware of the details to each ingredient being used by the SimpleMembershipProvider for the PBKDF2 function.  You might find that this is sufficient for what you need or you might find some points about it are worrisome and can make the call to roll your own provider or create you own membership system.  Some people like Brock Allen have written some pretty good thoughts on why it might be wise to really evaluate your applications needs and determine if it’s a right fit.  But again, you can’t make that evaluation if you don’t know what is going on under the covers and have a good grasp on all that is involved with password hashing.

Be Lazy, Be Simple, SimpleMembershipProvider first appeared on LockMeDown.com

About the author

Max McCarty

Max McCarty is a software developer with a passion for breathing life into big ideas. He is the founder and owner of LockMeDown.com and host of the popular Lock Me Down podcast.