2019年5月24日 星期五

The right way to implement password hashing using PBKDF2 and C#

Following from my previous post about hashing using BCrypt and in response to some comments I received on Google+, I decide to provide an alternative hashing implementation using PBKDF2.
As you will notice, the implementation is somewhat bigger than the one provided for BCrypt but in effect, both code segments perform the same task. First we create a hash from the plain text password and then we validate a password against the stored hash.
NOTE: The constants, like the iterations, can be changed to tweak the hash strength.
public class PasswordHash
{
public const int SaltByteSize = 24;
public const int HashByteSize = 20; // to match the size of the PBKDF2-HMAC-SHA-1 hash
public const int Pbkdf2Iterations = 1000;
public const int IterationIndex = 0;
public const int SaltIndex = 1;
public const int Pbkdf2Index = 2;
public static string HashPassword(string password)
{
var cryptoProvider = new RNGCryptoServiceProvider();
byte[] salt = new byte[SaltByteSize];
cryptoProvider.GetBytes(salt);
var hash = GetPbkdf2Bytes(password, salt, Pbkdf2Iterations, HashByteSize);
return Pbkdf2Iterations + ":" +
Convert.ToBase64String(salt) + ":" +
Convert.ToBase64String(hash);
}
public static bool ValidatePassword(string password, string correctHash)
{
char[] delimiter = { ':' };
var split = correctHash.Split(delimiter);
var iterations = Int32.Parse(split[IterationIndex]);
var salt = Convert.FromBase64String(split[SaltIndex]);
var hash = Convert.FromBase64String(split[Pbkdf2Index]);
var testHash = GetPbkdf2Bytes(password, salt, iterations, hash.Length);
return SlowEquals(hash, testHash);
}
private static bool SlowEquals(byte[] a, byte[] b)
{
var diff = (uint)a.Length ^ (uint)b.Length;
for (int i = 0; i < a.Length && i < b.Length; i++)
{
diff |= (uint)(a[i] ^ b[i]);
}
return diff == 0;
}
private static byte[] GetPbkdf2Bytes(string password, byte[] salt, int iterations, int outputBytes)
{
var pbkdf2 = new Rfc2898DeriveBytes(password, salt);
pbkdf2.IterationCount = iterations;
return pbkdf2.GetBytes(outputBytes);
}
}
The code above is pretty self explanatory. You call PasswordHash.HashPassword(plaintext) to get the hash back and then you call PasswordHash.ValidatePassword(plainText, storedHash) to check if the supplied password matches the originally supplied one by the user.
Personally, I'm a fan of BCrypt for its simplicity, but it is nice to know that there are two ways to achieve the same thing.
What is your preference? Did you find this useful? Let me know in the comments.

from : https://cmatskas.com/-net-password-hashing-using-pbkdf2/

沒有留言:

張貼留言