19

I created a simple MVC4 app and registered a user. The usrname and password are stored in a table called: AspNetUsers. This table does not have a salt field.

The way I understood is that when a user logs in; they enter a username and password. The salt is then concatenated with the password entered and compared to the password in the database. Is that not correct? i.e.

Hash(PasswordEntered) + Salt = Password in database = authenticated
Hash(PasswordEntered) + Salt <> Password in database = not authenticated

There is a field called: aspnetusers.SecurityStamp, however my research tells me that this is not the Salt.

Update

I have just read Scott Chamberlain. Please see the steps below:

1) A user enters: Hello123 as the password during registration and the Salt (randomly generated) is: 456, then the password entered into PasswordHash is: Hello123+456
2) The user then attempts to login and types Hello123 (correctly) as the password. The salt (randomly generated) is: 567. Therefore Hello123+456 is compared to Hello123+567 and the authentication fails.

In this case the user enters the correct password and is not authenticated. I am obviously missing something fundamental here.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
w0051977
  • 15,099
  • 32
  • 152
  • 329

2 Answers2

15

You have the pattern incorrect, the correct one would be

Hash(PasswordEntered + Salt)  = hash in database = authenticated
Hash(PasswordEntered + Salt)  <> hash in database = not authenticated

The way the provider for ASP.net works is it stores Salt + Hash(PasswordEntered + Salt) in the password field. So when you go to test a password you just use the part before the separator in the salt and compare it to the part after the separator.

In your update the part you have wrong is there is no randomly generated salt when the user logs in. It re-uses the salt that was randomly generated at registration for the user, that salt is stored in plain-text in the database and is not hashed.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • Thanks. In that case the Salt value must be the same for every user (as there is no way of identifying it on a per user basis). Is that correct? +1 for correcting the formula. – w0051977 Dec 29 '16 at 14:23
  • 4
    No, For a salt to be useful every hashing should have a unique salt. The value that was used for the salt is not secret information and does not need to be hidden. See [this answer](http://stackoverflow.com/a/21496255/80274) for more specifics on how it is stored. – Scott Chamberlain Dec 29 '16 at 14:24
  • 1
    No, the salt is stored in the same column alongside the salted and hashed password – phuzi Dec 29 '16 at 14:25
  • @Scott Chamberlain, I have updated the question based on your answer. Could you take a look? I am nearly there. – w0051977 Dec 29 '16 at 14:31
  • 2
    Usually just stored in another database field or just appended in front of the hash. The salt is not secret. Say for example I have a salt of 1234, in my database I would have stored `1234|avsSAFwa41s==` so `1234` is the salt, and `avsSAFwa41s==` is the base64 encoded password. When I go to verify the password I do `Hash(1234 + user provided password)` and check that it matches `avsSAFwa41s==` – Scott Chamberlain Aug 11 '19 at 06:26
  • @w0051977 see my updated comment. The part you have wrong is the login of the user does not have a randomly generated salt. It uses the salt saved in plaintext in the database as part of the calculation. – Scott Chamberlain Aug 11 '19 at 06:33
5

Same for asp.net core 3.

Salt + Hash are joined and stored into the passwordHash field in the db.

Here is the source code of how the passwordHash is being generated.

*password parameter is the plaintext password.

*subkey is the hash.

https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/PasswordHasher.cs

private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested)
{
    // Produce a version 3 (see comment above) text hash.
    byte[] salt = new byte[saltSize];
    rng.GetBytes(salt);
    byte[] subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

    var outputBytes = new byte[13 + salt.Length + subkey.Length];
    outputBytes[0] = 0x01; // format marker
    WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
    WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount);
    WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize);
    Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
    Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
    return outputBytes;
}
Jack Ng
  • 473
  • 7
  • 14