After stating that hashing passwords is easy, in reference to some high profile companies suffering leaks, we're sharing some information about steps we're taking.

How we hash passwords

In the previous post I made the claim that hashing passwords is not difficult. The post was spurred by several recent high profile password breaches.

Sticking to what I said in that post about hashing being easy, it was necessary to back that up. So here's how we go about hashing passwords. I'll be pointing a few other software developers at this post, as part of a wider code review of something important such as this.

These are the defaults, in practice we're using a larger iteration count than 1000, but from the information out there, 1000 seems to be sufficient these days. The minimum recommended entropy for a salt is 64 bits, but just to be sure we're using 128 bits. The following is the contents of a class that is used in the following way.

Creating and storing the hash

var salt = CreateSalt();

var hash = HashPassword("password", salt);

db.Insert(new User { Id = 1, PasswordHash = hash, Salt = salt });

And looks like this when stored:

DB Storage

Comparing supplied password to stored hash

var user = db.FetchByUserName<User>(suppliedUserName);

if (IsCorrectPassword(suppliedPasswordToTest, user.Salt, user.PasswordHash))
{
    // we're good
}

That's it, storage on creation (/password updated) and retrieval with a comparison as part of granting access. The database logic is simplified to get the point across easier.

Code

The code below makes use of Rfc2898DeriveBytes which is an implementation of PBKDF2, using a pseudo-random number generator based on HMACSHA1.

Defaults

public const int DefaultIterations = 1000;
public const int HashBytes = 128;

Salt

Creating a Salt, per user never re-used. Takes the iteration count, so it can be stored in the format of itteration:hash.

public static string CreateSalt(int? explicitIterations = null)
{
    var iterations = explicitIterations ?? DefaultIterations;

    var randomArray = new byte[HashBytes];

    new RNGCryptoServiceProvider().GetBytes(randomArray);

    return iterations.ToString("X") + ":" + Convert.ToBase64String(randomArray);
}

Hash

Now the actual hashing, taking the salt, iterations, and the cleartext password. Extract out the iteration count prefix on the salt first, this approach allows for non-hard-coded iteration, that way if you increase it in the future, you can have older passwords with their old lower iteration count, until the user decides to change their password.

public static byte[] HashPassword(string password, string salt)
{
    var i = salt.IndexOf(':');
    var iteration = int.Parse(salt.Substring(0, i), System.Globalization.NumberStyles.HexNumber);
    salt = salt.Substring(i + 1);

    var pbkdf2 = new Rfc2898DeriveBytes(
        Encoding.UTF8.GetBytes(password), 
        Convert.FromBase64String(salt), 
        iteration);

    return pbkdf2.GetBytes(HashBytes);
}

Comparison

There are many ways to do the comparison code.

The simplest way is to use SequenceEqual in System.Linq Enumerable.SequenceEqual<TSource>(IEnumerable<TSource>, IEnumerable<TSource>)

public static bool IsCorrectPassword(string password, string salt, byte[] retrievedHash)
{
    var testHash = HashPassword(password, salt);

    return retrievedHash.SequenceEqual(testHash);
}

Some other popular ways can be seen on this StackOverflow answer and this one.

Conclusion

Keep this code as simple as possible, in our production code we have a few additional checks to throw exceptions for null / empty strings for password and salt values. But other then that this is quite standard code out there in use in many pieces of software.

Nick Josevski
Posted by: Nick Josevski  
Last revised: 20 Feb, 2013 12:30 AM
 

Comments

No comments yet. Be the first!

blog comments powered by Disqus