Last active
July 12, 2019 21:18
-
-
Save elizabeth-young/5363087 to your computer and use it in GitHub Desktop.
Password methods to hash and validate a password
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class PasswordHash | |
{ | |
private const int SALT_BYTES = 24; | |
private const int HASH_BYTES = 24; | |
private const int PBKDF2_ITERATIONS = 1000; | |
private const int ITERATION_INDEX = 0; | |
private const int SALT_INDEX = 1; | |
private const int PBKDF2_INDEX = 2; | |
/// <summary> | |
/// Creates a salted PBKDF2 hash of the password. | |
/// </summary> | |
/// <param name="password">The password to hash.</param> | |
/// <returns>The hash of the password.</returns> | |
public static string CreateHash(string password) | |
{ | |
// Generate a random salt | |
RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider(); | |
byte[] salt = new byte[SALT_BYTES]; | |
csprng.GetBytes(salt); | |
// Hash the password and encode the parameters | |
byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES); | |
return PBKDF2_ITERATIONS + ":" + | |
Convert.ToBase64String(salt) + ":" + | |
Convert.ToBase64String(hash); | |
} | |
/// <summary> | |
/// Validates a password given a hash of the correct one. | |
/// </summary> | |
/// <param name="password">The password to check.</param> | |
/// <param name="goodHash">A hash of the correct password.</param> | |
/// <returns>True if the password is correct. False otherwise.</returns> | |
public static bool ValidatePassword(string password, string goodHash) | |
{ | |
// Extract the parameters from the hash | |
char[] delimiter = { ':' }; | |
string[] split = goodHash.Split(delimiter); | |
int iterations = Int32.Parse(split[ITERATION_INDEX]); | |
byte[] salt = Convert.FromBase64String(split[SALT_INDEX]); | |
byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]); | |
byte[] testHash = PBKDF2(password, salt, iterations, hash.Length); | |
return SlowEquals(hash, testHash); | |
} | |
/// <summary> | |
/// Compares two byte arrays in length-constant time. This comparison | |
/// method is used so that password hashes cannot be extracted from | |
/// on-line systems using a timing attack and then attacked off-line. | |
/// </summary> | |
/// <param name="a">The first byte array.</param> | |
/// <param name="b">The second byte array.</param> | |
/// <returns>True if both byte arrays are equal. False otherwise.</returns> | |
private static bool SlowEquals(byte[] a, byte[] b) | |
{ | |
uint 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; | |
} | |
/// <summary> | |
/// Computes the PBKDF2-SHA1 hash of a password. | |
/// </summary> | |
/// <param name="password">The password to hash.</param> | |
/// <param name="salt">The salt.</param> | |
/// <param name="iterations">The PBKDF2 iteration count.</param> | |
/// <param name="outputBytes">The length of the hash to generate, in bytes.</param> | |
/// <returns>A hash of the password.</returns> | |
private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes) | |
{ | |
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt); | |
pbkdf2.IterationCount = iterations; | |
return pbkdf2.GetBytes(outputBytes); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment