Created
March 20, 2013 20:36
-
-
Save matanlurey/5208199 to your computer and use it in GitHub Desktop.
This file contains 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
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