The SimpleMembershipProvider, Secure Passwords And The Crypto Helper

Some people have questioned the security of the ASP.NET SimpleMembershipProvider's storage of passwords. The cause for concern seems to stem from the fact that the PasswordSalt field in the standard membership table is unused by the SimpleMembershipProvider, whereas it is used by, for example, SqlMembershipProvider. So what is the PasswordSalt field, and why should the fact that it isn't used raise an eyebrow?

Everyone knows that you should not store user passwords in plain text, right? Doing so is highly irresponsible. It may be that the content you offer is not high value, and that if someone manages to compromise your password store, they won't really profit from using any of the credentials to log in to restricted areas in your site. However, it is a fact that many people reuse passwords across multiple sites, so you may end up providing the malicious user with someone's keys to another kingdom altogether. If you are asking people to provide their personal information to you, then you owe those people a duty of care.

So how should you store passwords? Generally, the accepted way to store a password is not to store it at all. You should instead store a hash. A hash function is an algorithm that takes a block of data, such as a string, and generates a new value, or hash, based on the input. Hashes are fixed size. They are also deterministic. That means that the same value subjected to the same algorithm will always result in the same hash. When a user first registers with your site, you take their proffered password, hash it and store the hash in a database. Whenever they log in after that, you take their submitted password, hash it and compare that hash to the one stored in the database.

Hashing is one-way. It should be infeasible to compute the original value from a given hash. People have a habit of using real words and names as passwords. The chances of duplicate passwords grows when real words are used, and since hashing is deterministic, the hash will be identical for those users that share the same password. The presence of duplicate hashes in a password table is a good indicator that real words or names were used. Hackers can use a number of techniques to attempt to crack hashed passwords, including Brute Force, Dictionary Attacks and Rainbow Tables. All of these approaches are ineffective against hashes that include "salts".

In cryptographic terms, a salt is a random value that is concatenated with the password before hashing it. If you salt your hashes, the salt itself needs to be included with the password prior to hashing it for comparison with the stored value. Now you begin to see why people are concerned about the SimpleMembershipProvider - since the PasswordSalt field in the database is not used, it looks like password hashes are not salted and are therefore not as secure as they should be.

The good news is that appearances can be deceptive, and that the SimpleMembershipProvider does indeed salt hashes. In fact, the SimpleMembershipProvider makes use of the little known Crypto Helper whose HashPassword method is of most interest in relation to this article:

/* =======================
 * HASHED PASSWORD FORMATS
 * =======================
 * 
 * Version 0:
 * PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
 * (See also: SDL crypto guidelines v5.1, Part III)
 * Format: { 0x00, salt, subkey }
 */

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);
}

This method takes the password, and passes it to the constructor of a Rfc2898DeriveBytes class. The class constructor also needs the size of the salt it should generate (128 bits) and the number of times it should apply the algorithm to the password/salt combination to generate a secure hash. It generates its own cryptographically strong salt and returns that as a byte array along with the hash. The method then constructs a new byte array consisting of (in order) one empty byte (0x00), 16 bytes containing the salt value, and a further 32 bytes containing the hashed salt + password. This is then encoded to a Base64 string for return by the method, and that is the value stored in the Password field.

The Crypto helper also offers another method - VerifyHashedPassword:

// hashedPassword must be of the format of HashWithPassword (salt + Hash(salt+input)
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);
}

This method is provided with the password that the user entered on logging in, and the value that is stored in the password field for the particular user. It then reverses part of the work done by the HashPassword method - it converts the Base64 encoded string back to a byte array, and then extracts the hash part of that array, and the salt. It then uses the salt to hash the submitted password, and compares the result to the retrieved hash.

If you only ever need simple (but secure) password storage and verification, you can use the Crypto helper methods without having to configure and use the SimpleMembershipProvider:

@{
    var db = Database.Open("MyDb");
    var username = Request["UserName"];
    var password = Request["Password"];
    var hash = Crypto.HashPassword(password);
    db.Execute("INSERT INTO Users (Username, Password) VALUES (@0, @1)", username, hash);
}

Other interesting methods within the helper offer the ability to generate SHA1 and SHA256 hashes as well as more generic methods that return hashes generated by the algorithm that you specify. There is also a utility method for generating salts.

So why is there an unused column named PasswordSalt in the standard membership table? Who knows, but my guess is that the schema was designed before the Crypto helper, and since the HashPassword method returns one string which represents the encoded and combined salt and the password hash, it was decided not to bother deconstructing that to populate the PasswordSalt field. Either that, or someone simply forgot to remove it.

 

Date Posted: Thursday, September 27, 2012 1:39 PM
Last Updated:
Posted by: Mikesdotnetting
Total Views to date: 23977

6 Comments

Wednesday, October 10, 2012 7:06 AM - Protected Identity

Heh. I always wondered why that Column is always empty. Thanks!

Thursday, January 3, 2013 4:29 AM - Zaf Khan

Hello Mike, Thank you for sharing your knowledge with us all. I wondered would it be possible if you could add an extra section at the bottom to show how we could use the hashing functions to confirm if the hashed data matches the data heldinthe db as opposed to inserting it as a new record? Thank You

Thursday, March 7, 2013 12:22 PM - sam

Very informative post; thanks for that.

Wednesday, April 10, 2013 9:15 PM - SINGHXLb

Thanks Mike but i have one question what are values for SaltSize and PBKDF2SubkeyLength ?

Wednesday, May 8, 2013 12:40 AM - Stuart Clement

Nice and simple article Mike!

Monday, February 3, 2014 10:25 PM - John

Thank you very much. Very comprehensive and straight to the point.
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

Gautam 11/20/2014 8:01 AM
In response to I'm Writing A Book On WebMatrix
Hello Mike, I read your book, loved it! However, I have a few request/suggestions: 1) an example...

Bret Dev 11/19/2014 8:39 PM
In response to The Difference Between @Helpers and @Functions In WebMatrix
Excellent post! One concern - where can you place global @Functions code within an MVC project to Is...

Rob Farquharson 11/19/2014 4:28 PM
In response to iTextSharp - Links and Bookmarks
How can I place text at an absolute position on the page? Also, how can I rotate text?...

Andy 11/17/2014 8:08 PM
In response to MVC 5 with EF 6 in Visual Basic - Sorting, Filtering and Paging
Hello I'm testing your sorting instructions above. This is great and I was able to get it to work...

Gautam 11/17/2014 5:51 PM
In response to WebMatrix - Database Helpers for IN Clauses
Hi Mike, I am very new to programming: In the above example if I want to use a delete button the...

donramon 11/17/2014 3:22 PM
In response to Entity Framework 6 Recipe - Alphabetical Paging In ASP.NET MVC
Congratulations on your new website look and the excellent articles. Thank you!...

Gautam 11/17/2014 11:26 AM
In response to Looking At The WebMatrix WebGrid
Hi Mike, I add the jquery script at the end of my html file.. when ajax attribute is added to the be...

Chet Ripley 11/15/2014 6:57 PM
In response to Adding A New Field
It appears the command is case sensitive. I had the same issue as Cameron. When I changed the to it...

Alvin 11/14/2014 12:49 PM
In response to Razor Web Pages E-Commerce - Adding A Shopping Cart To The Bakery Template Site
Great article Mike! When do you plan to extend the bakery shopping cart beyond this point?...

Gautam 11/14/2014 10:16 AM
In response to Web Pages - Efficient Paging Without The WebGrid
to get the count can we use only the below sql, why to join category and author table var sql =...