Friday, June 12, 2009

Reverse engineering ASP.NET Membership passwords and salts

I’m working on a project that was using the ASP.NET SQL Membership and I needed to remove the Membership provider from the system since we wanted more control over the user tables.  Our existing users had passwords that ASP.NET Membership had hashed and salted, and we needed to be able to maintain those user accounts, which means we have to be able to validate logins against the salted passwords. 

I understood how password salts work in general, but I could not find any documentation for how it was implemented exactly in ASP.NET Membership.  Fortunately it wasn’t too hard to figure out, and here is the method that can validate user passwords on the aspnet_Users table without using the Membership.ValidateUser method:

private static HashAlgorithm passwordHasher = HashAlgorithm.Create("SHA1");

private bool ValidateUser(string username, string password)
{
    var user = GlobalApplication.Database.Users.FirstOrDefault(u => u.UserName == username);
    if (user == null) return false;

    byte[] saltBytes = Convert.FromBase64String(user.Membership.PasswordSalt);
    byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
    byte[] bytesToHash = new byte[saltBytes.Length + passwordBytes.Length];
    saltBytes.CopyTo(bytesToHash, 0);
    passwordBytes.CopyTo(bytesToHash, saltBytes.Length);
    byte[] hash = passwordHasher.ComputeHash(bytesToHash);
    string base64Hash = Convert.ToBase64String(hash);
    return user.Membership.Password == base64Hash;
}

4 comments:

  1. For some reason I needed to change the Encoding from UTF8 to Unicode. Otherwise could use code as is. Thanks for the post.

    ReplyDelete
  2. This is the actual method used (thanks to Reflector):
    internal string EncodePassword(string pass, int passwordFormat, string salt)
    {
    if (passwordFormat == 0)
    {
    return pass;
    }
    byte[] bytes = Encoding.Unicode.GetBytes(pass);
    byte[] src = Convert.FromBase64String(salt);
    byte[] dst = new byte[src.Length + bytes.Length];
    byte[] inArray = null;
    Buffer.BlockCopy(src, 0, dst, 0, src.Length);
    Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
    if (passwordFormat == 1)
    {
    HashAlgorithm algorithm = HashAlgorithm.Create(Membership.HashAlgorithmType);
    if ((algorithm == null) && Membership.IsHashAlgorithmFromMembershipConfig)
    {
    RuntimeConfig.GetAppConfig().Membership.ThrowHashAlgorithmException();
    }
    inArray = algorithm.ComputeHash(dst);
    }
    else
    {
    inArray = this.EncryptPassword(dst);
    }
    return Convert.ToBase64String(inArray);
    }

    ReplyDelete
  3. Thanks for posting this - Works great, although as per Toms post, I had to change to Unicode for it to work.

    ReplyDelete
  4. Thank you for this article. I needed implementation in Java - so here is working example:

    public static String generateHash(String password, String salt) throws Exception {
    byte[] saltBytes = Base64.decodeBase64(salt);
    byte[] passwordBytes = password.getBytes("UTF-16LE");

    byte[] bytesToHash = new byte[saltBytes.length + passwordBytes.length];

    System.arraycopy(saltBytes, 0, bytesToHash, 0, saltBytes.length);
    System.arraycopy(passwordBytes, 0, bytesToHash, saltBytes.length, passwordBytes.length);

    MessageDigest md = MessageDigest.getInstance("SHA-1");
    byte[] hash = md.digest(bytesToHash);
    String result = Base64.encodeBase64String(hash);

    return result;
    }

    ReplyDelete