Last active
October 3, 2016 00:41
-
-
Save bricef/31f4440858556aa08cf02cac9acc5086 to your computer and use it in GitHub Desktop.
Salting and Hashing passwords correctly in .NET
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
/** | |
* From http://lockmedown.com/hash-right-implementing-pbkdf2-net/ | |
*/ | |
public class Hash | |
{ | |
private const int SaltByteLength = 32; | |
private const int DerivedKeyLength = 32; | |
private const int IterationCount = 48000; | |
private const char separator = '|'; | |
private static RNGCryptoServiceProvider CRNG = new RNGCryptoServiceProvider(); | |
public static string Encode(string password) | |
{ | |
var salt = GenerateSalt(); | |
return EncodeWithSaltAndHash(password, salt, IterationCount); | |
} | |
public static Boolean Compare(string password, string encodedPassword) | |
{ | |
var salt = SaltFromEncoded(encodedPassword); | |
var iterations = IterationsFromEncoded(encodedPassword); | |
var encoded = EncodeWithSaltAndHash(password, salt, iterations); | |
return ConstantTimeComparison(encoded, encodedPassword); | |
} | |
private static bool ConstantTimeComparison(string passwordGuess, string actualPassword) | |
{ | |
uint difference = (uint)passwordGuess.Length ^ (uint)actualPassword.Length; | |
for (var i = 0; i < passwordGuess.Length && i < actualPassword.Length; i++) | |
{ | |
difference |= (uint)(passwordGuess[i] ^ actualPassword[i]); | |
} | |
return difference == 0; | |
} | |
private static byte[] SaltFromEncoded(string encoded) | |
{ | |
string[] parts = encoded.Split(separator); | |
return Convert.FromBase64String(parts[0]); | |
} | |
private static int IterationsFromEncoded(string encoded) | |
{ | |
string[] parts = encoded.Split(separator); | |
return Convert.ToInt32(parts[2]); | |
} | |
private static string EncodeWithSaltAndHash(string password, byte[] salt, int iterations) | |
{ | |
var hash = GenerateHash(password, salt, iterations); | |
var b64salt = Convert.ToBase64String(salt); | |
var b64hash = Convert.ToBase64String(hash); | |
return b64salt + separator + b64hash + separator + Convert.ToString(IterationCount); | |
} | |
private static byte[] GenerateSalt() | |
{ | |
byte[] bytes = new byte[8]; | |
CRNG.GetBytes(bytes); | |
return bytes; | |
} | |
private static byte[] GenerateHash(string password, byte[] salt, int iterationCount) | |
{ | |
byte[] hashValue; | |
var valueToHash = string.IsNullOrEmpty(password) ? string.Empty : password; | |
using (var pbkdf2 = new Rfc2898DeriveBytes(valueToHash, salt, iterationCount)) | |
{ | |
hashValue = pbkdf2.GetBytes(DerivedKeyLength); | |
} | |
return hashValue; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment