Salted Password Hashing - Doing it Right
Last active
December 14, 2015 00:59
-
-
Save smonn/5003278 to your computer and use it in GitHub Desktop.
Encrypt password with random salt, and example of slow equals.
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.Linq; | |
using System.Security.Cryptography; | |
*/ | |
// The user's password in pure text. | |
var password = string.Empty; | |
// Generate a random salt, this should be stored in its own column in the database, | |
// preferably in a binary format (i.e. binary(32) in this case, adjust length accordingly). | |
// The salt should be at least as long as the generated hash, for SHA256 that is 32 bytes. | |
var rng = new RNGCryptoServiceProvider(); | |
var salt = new byte[32]; | |
// Basically, this will fill the salt array with random bytes. | |
rng.GetBytes(salt); | |
// Use at least SHA256 since SHA1 is very weak in comparison. | |
var sha256 = new SHA256CryptoServiceProvider(); | |
// We don't care about encoding in this scenario, and using Buffer.BlockCopy saves | |
// us a tiny bit of memory and time over Encoding.XXX.GetBytes. | |
var passwordBytes = new byte[password.Length * sizeof(char)]; | |
Buffer.BlockCopy(password.ToCharArray(), 0, passwordBytes, 0, passwordBytes.Length); | |
// Compute the password hash (this step is kind of optional, but the more we | |
// hash the more difficult it will be for attackers). | |
var hash = sha256.ComputeHash(passwordBytes); | |
// Prepend the salt and hash it all one more time. Mandatory step. | |
var bytes = sha256.ComputeHash(salt.Concat(hash).ToArray()); | |
// What we have in the bytes variable should now be stored in the database in its own column. | |
// Just like the salt, store it in binary format. | |
// The reason for storing in binary format is basically to save us from having to convert the hash | |
// to a hexadecimal string and store that in the database (or worse, using Encoding.XXX.GetString). | |
// A simple way to calculate the hexadecimal string, however, would be to use something like this: | |
var hex = new string(bytes.SelectMany(x => x.ToString(@"X2").ToCharArray()).ToArray()); | |
// Another option would be to use Convert.ToBase64String (a benefit with this approach is that | |
// we can convert it back to a byte array easily): | |
var base64 = Convert.ToBase64String(bytes); | |
// When comparing two byte arrays it is suggested to use a slow equals. | |
// This is done by comparing the entire array: | |
var a = bytes; | |
var b = bytes; // This should obivously be generated from a login attempt. | |
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]); | |
if (diff == 0) | |
Console.WriteLine("The passwords match!"); | |
else | |
Console.WriteLine("Wrong password"); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment