Friday 21 December 2012

Slow vs Fast Hashing Algorithms in C#

In a project I'm working on at the moment the passwords were hashed using the MD5 hash, in fact the code is pretty much line for line the code found here, one problem with this code. 

There is no salting, which means that users using the same passwords would have the same password, e.g. QWERT12345 will always hash to bb760a3ee6d5abe0b0e58d7977d25527.

A bigger problem, this hashing algorithm is incredibly fast. My ancient Intel p8600 @ 2.4 GHz takes about 18 seconds to calculate 1 million of these hashes, hashing QWERT12345, and this is running on a single core and this is absolutely nothing to write home about, compared to what a GPU can do, see this article.

There are quite a few options to solve this problem, namely using a more secure way of hashing passwords, but I chose using PBKDF2, mainly because it's part of the framework.

This method allows changes to the number of iterations and/or the salt length. Ensure that the separator is not part of the base64 character set, namely don't use +,/ or =.

Here is the code I used:

public const int keyLength = 32;
public const int iterations = 10000;
public string PBKDF2Password(string password)
{
    StringBuilder sb = new StringBuilder();

    using (var bytes = new Rfc2898DeriveBytes(password, keyLength, iterations))
    {
        byte[] salt = bytes.Salt;
        byte[] key = bytes.GetBytes(keyLength);

        sb.Append(string.Format("{0}:", iterations));
        sb.Append(string.Format("{0}:", Convert.ToBase64String(salt)));
        sb.Append(string.Format("{0}", Convert.ToBase64String(key)));

    }

    return sb.ToString();
}

Using this method, with the above values, it takes my laptop 0.3 seconds to calculate a single salted hash, which roughly means that we've gone from 50k hashes per second to 3 per second. The beauty of this hashing algorithm is that you can increase the number of iterations as hardware gets more and more powerful, so now you just need to decide how long you want your users to wait before they can log in to your system.

A method like this can be used for checking the password for validity.

public bool CheckPassword(string password, string hash)
{
    bool result = false;
    byte[] saltArray, key;
    int iterations;

    string[] hashComponents = hash.Split(':');

    iterations = Convert.ToInt32(hashComponents[0]);
    saltArray = Convert.FromBase64String(hashComponents[1]);
    key = Convert.FromBase64String(hashComponents[2]);

    using (var bytes = new Rfc2898DeriveBytes(password, saltArray, iterations))
    {
        byte[] newKey = bytes.GetBytes(keyLength);

        if (newKey.SequenceEqual(key))
        {
            result = true;
        }
    }
    return result;
}

No comments:

Post a Comment