Skip to content

Instantly share code, notes, and snippets.

@matanlurey
Created March 20, 2013 20:36
Show Gist options
  • Save matanlurey/5208202 to your computer and use it in GitHub Desktop.
Save matanlurey/5208202 to your computer and use it in GitHub Desktop.
using System;
using System.Security.Cryptography;
using System.Text;
namespace Com.Rakuten.Services.Common.Secure
{
/// <summary>
/// Salted password hashing with PBKDF2-SHA1
/// </summary>
/// <remarks>
/// Protection against hash cracking, rainbow tables, timing attacks, and more8
/// </remarks>
public static class PasswordHash
{
// Following constants may be changed *without* breaking existing hashes.
private const int SaltBytesLength = 24;
private const int HashBytesLength = 24;
private const int PBKDF2IterationCount = 1000;
// Do not modify
private const char Delimiter = ':';
private const int IterationIndex = 0;
private const int SaltIndex = 1;
private const int PBKDF2Index = 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
var salt = new byte[SaltBytesLength];
new RNGCryptoServiceProvider().GetBytes(salt);
// Hash the password and encode the password
var hash = PBKDF2(password: password,
salt: salt,
iterationCount: PBKDF2IterationCount,
outputBytesLength: HashBytesLength);
// Return hash and parameters
return new StringBuilder(PBKDF2IterationCount)
.Append(Delimiter)
.Append(Convert.ToBase64String(salt))
.Append(Delimiter)
.Append(Convert.ToBase64String(hash))
.ToString();
}
/// <summary>
/// Validates a password given a hash of the correct one.
/// </summary>
/// <param name="password">The (plaintext) password to check.</param>
/// <param name="hash">A hash of the correct password.</param>
/// <returns>True if the password is correct. False otherwise.</returns>
public static bool ValidatePassword(string password, string hash)
{
// Extract parameters from the hash
var split = hash.Split(Delimiter);
var iterationCount = Int32.Parse(split[IterationIndex]);
var salt = Convert.FromBase64String(split[SaltIndex]);
var pbkdf2 = Convert.FromBase64String(split[PBKDF2Index]);
// Convert incoming password to hash using the same parameters
var test = PBKDF2(password, salt, iterationCount, pbkdf2.Length);
// Compare
return SlowEquals(pbkdf2, test);
}
/// <summary>
/// Computes that PBKDF2-SHA1 hash of a password
/// </summary>
/// <param name="password">The password to hash.</param>
/// <param name="salt">The salt.</param>
/// <param name="iterationCount">The PBKDF2 iteration count.</param>
/// <param name="outputBytesLength">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 iterationCount, int outputBytesLength)
{
return new Rfc2898DeriveBytes(password, salt, iterationCount).GetBytes(outputBytesLength);
}
/// <summary>
/// Compares two byte arrays in length-constant time
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
private static bool SlowEquals(byte[] a, byte[] b)
{
var delta = (uint) a.Length ^ (uint) b.Length;
for (var i = 0; i < a.Length && i < b.Length; ++i)
delta |= (uint) (a[i] ^ b[i]);
return delta == 0;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment