Asp.net Identity password hashing

HEALTH WARNING for the below answer: Know which version of ASP.Net Identity you are using. You should refer to the source code directly if it is one of the newer versions from the github repository.

As I write this, the current version (3.0.0-rc1/…/PasswordHasher.cs) of the password handler is significantly different to the below answer. This newer version supports multiple hash algorithm versions and is documented as (and may change further by the time you read this):

Version 2:

  • 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 }

Version 3:

  • PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
  • Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
  • (All UInt32s are stored big-endian.)

The original answer is still valid for the original version of ASP.Net Identity, and is as follows:


@jd4u is correct, but to shed a little more light which wouldn’t fit into a comment for his answer:

  • Microsoft.AspNet.Identity.PasswordHasher : IPasswordHasher already salts for you,
    • more importantly it uses Rfc2898DeriveBytes to generate the salt and the hash,
    • which uses industry standard PBKDF2 (SE discussion here, OWASP recommendation for PBKDF2 here).
  • The default Microsoft.AspNet.Identity.UserManager<TUser> implementation uses Microsoft.AspNet.Identity.PasswordHasher as a concrete IPasswordHasher
  • PasswordHasher in turn is a really simple wrapper for (ultimately)System.Security.Cryptography.Rfc2898DeriveBytes

So, if you are going to use Rfc2898DeriveBytes, just use PasswordHasher – all the heavy lifting is already done (hopefully correctly) for you.

Details

The full code that PasswordHasher (currently) ultimately uses does something very close to:

int saltSize = 16;
int bytesRequired = 32;
byte[] array = new byte[1 + saltSize + bytesRequired];
int iterations = SOME; // 1000, afaik, which is the min recommended for Rfc2898DeriveBytes
using (var pbkdf2 = new Rfc2898DeriveBytes(password, saltSize, iterations))
{
    byte[] salt = pbkdf2.Salt;        
    Buffer.BlockCopy(salt, 0, array, 1, saltSize);
    byte[] bytes = pbkdf2.GetBytes(bytesRequired);
    Buffer.BlockCopy(bytes, 0, array, saltSize+1, bytesRequired);
}
return Convert.ToBase64String(array);

Leave a Comment