Search code examples
c#encryptionasp.net-core-mvcpassword-encryption

Why is my password hashing function producing different hashes with the same salt?


Passwords stored in my database are encrypted like this:

byte[] salt = NewSalt();
string hashedPassword = HashPassword("passwordInTheClear", salt);
// Add/update the admin-user with hashed password and salt.

The hashing-function:

public static string HashPassword(string password, byte[] salt)
{
    // derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
    return Convert.ToBase64String(KeyDerivation.Pbkdf2(
        password: password,
        salt: salt,
        prf: KeyDerivationPrf.HMACSHA1,
        iterationCount: 10000,
        numBytesRequested: 256 / 8));
}

The salt-generator:

public static byte[] NewSalt()
{
    // generate a 128-bit salt using a secure PRNG
    byte[] salt = new byte[128 / 8];
    using (var rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(salt);
    }
    return salt;
}

When a user is trying to log in to the system, I am hashing the entered password from the login form, using the same hash function and the same salt, and comparing it to the hashed password stored in the database:

// (I have separated out the password check from the linq query just for debugging purposes)
AdminUser au = await db.AdminUsers
    .Where(u =>
        u.Email1 == loginForm.UserName)
    .FirstOrDefaultAsync().ConfigureAwait(false);
byte[] salt = Encoding.ASCII.GetBytes(au.PasswordSalt);
string hashedEnteredPassword = HashPassword(loginForm.Password, salt);
if (au.Password == hashedEnteredPassword)
{
    // Success!
}

But the stored and entered password does not match.

Example:

In the database:
Unhashed password: 1234
Salt: Cda6ZgNVluChtzseyq9uMQ==
Hashed password: PKzE3rr9CGGmVW3UJS1N7mqrXmzni3hsqyCtP8lrehE=

In the login form:
Entered, unhashed password: 1234
Salt: Cda6ZgNVluChtzseyq9uMQ==
Hashed password: WwYUZqV1GfuRKEitpRdKDjTMEGWy+1nYzpkWI+eZPB0=

Solution

  • You are getting your salt from the database as ASCII, while the salt in your example is clearly Base64. You just have to replace Encoding.ASCII.GetBytes(au.PasswordSalt) with Convert.FromBase64String(au.PasswordSalt) and call it a day.

    byte[] salt = Encoding.ASCII.GetBytes("Cda6ZgNVluChtzseyq9uMQ==");
    string encryptedPassword = EncryptPassword("1234", salt);
    Console.WriteLine(encryptedPassword);
    

    will give you WwYUZqV1GfuRKEitpRdKDjTMEGWy+1nYzpkWI+eZPB0=, while

    byte[] salt = Convert.FromBase64String("Cda6ZgNVluChtzseyq9uMQ==");
    string encryptedPassword = EncryptPassword("1234", salt);
    Console.WriteLine(encryptedPassword);
    

    gives PKzE3rr9CGGmVW3UJS1N7mqrXmzni3hsqyCtP8lrehE=.