Michael Seifert
Michael Seifert

Reputation: 182

Good practice web authentication with PBKDF2 and nonce in .NET

I'm building a web application and need to authenticate a user with a user password. I'm trying to build it to what would be considered a good security practice in 2021. As far as I've been able to gather from what I've read online, the following would be expected from sending the password from the client to the server over HTTPS (only).

[Edit: Context about the server] On the server side I intend to store a salt per user and a hashed version of their password. On the wire I obviously shouldn't send the clear text password, but also, to prevent playbacks, I shouldn't send the hashed password value either. Hence the client side algorithm below. [End edit]

  1. User's password is hashed on the client [Edit: with the same salt as used server side].
  2. Nonce is generated on the client [Edit: This should be server generated and given to the client, see comment]
  3. The hashed password plus nonce is hashed on the client.
  4. The nonce and final hash is sent from the client to the server over HTTPS.
  5. Be sure to cleanup the password on the client (not in my code example).

Here is my experimental sample code:

public const int HASH_SIZE = 24; // size in bytes
public const int ITERATIONS = 100000; // number of pbkdf2 iterations
public const int NONCE_SIZE = 8; // size in bytes

public static string PasswordFlow(string userPassword, byte[] userSalt)
{
    // Hash the user password + user salt
    var hpwd = KeyDerivation.Pbkdf2(userPassword, userSalt, KeyDerivationPrf.HMACSHA512, ITERATIONS, HASH_SIZE);

    // Generate an 8 byte nonce using RNGCryptoServiceProvider
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] nonce = new byte[NONCE_SIZE];
    rng.GetBytes(nonce);

    // Hash the hpwd byte[] converted to Base64 with the nonce byte array as salt
    var final = KeyDerivation.Pbkdf2(Convert.ToBase64String(hpwd), nonce, KeyDerivationPrf.HMACSHA512, ITERATIONS, HASH_SIZE);

    return Convert.ToBase64String(nonce)+"$"+ Convert.ToBase64String(final);
}

I would appreciate thoughts on the process above. Did I misunderstand it, screw it up or miss anything? I'm also trying to understand:

I'm not a security expert and I'm also a C# noob so please excuse any blunders.

Upvotes: 0

Views: 643

Answers (1)

roscoe
roscoe

Reputation: 271

PBKDF2 is designed to reduce brute-force attacks by increasing computational cost. It is not intended to resolve problem of sending plaintext password - this should be done by other security mechanism - secure communication (i.e. TLS 1.3).

If secure communication is broken, then it does not matter if you have sent plaintext or hash of the password.

What you are referring as NONCE should be called SALT.

Basically, PBKFD2:

  1. Takes any data you send (i.e. password)
  2. Adds SALT
  3. Applies PRF (Pseudo-Random Function) number of times
  4. Returns n-bits of derived password

So, answering your questions:

  1. It is ok to run PBKDF2 twice, however I would increase number of iterations, rather than run it twice
  2. 100,000 is reasonable number of iterations
  3. 24 bytes (192 bits) is reasonable hash size. Although you are using HMACSHA512 as PFR which produces hash of size 512 bits.
  4. PBKDF2 standard allows 8 bytes SALT, however NIST recommends min. 16 bytes - I would increase SALT size
  5. As mentioned earlier, you can run PBKDF2 on any string input. In most cases it would be password or passphrase

Upvotes: 2

Related Questions