Actually this is kind of strange, with the string conversions - which the membership provider does to put them into config files. Hashes and salts are binary blobs, you don't need to convert them to strings unless you want to put them into text files.
In my book, Beginning ASP.NET Security, (oh finally, an excuse to pimp the book) I do the following
static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)
{
HashAlgorithm algorithm = new SHA256Managed();
byte[] plainTextWithSaltBytes =
new byte[plainText.Length + salt.Length];
for (int i = 0; i < plainText.Length; i++)
{
plainTextWithSaltBytes[i] = plainText[i];
}
for (int i = 0; i < salt.Length; i++)
{
plainTextWithSaltBytes[plainText.Length + i] = salt[i];
}
return algorithm.ComputeHash(plainTextWithSaltBytes);
}
The salt generation is as the example in the question. You can convert text to byte arrays using Encoding.UTF8.GetBytes(string)
. If you must convert a hash to its string representation you can use Convert.ToBase64String
and Convert.FromBase64String
to convert it back.
You should note that you cannot use the equality operator on byte arrays, it checks references and so you should simply loop through both arrays checking each byte thus
public static bool CompareByteArrays(byte[] array1, byte[] array2)
{
if (array1.Length != array2.Length)
{
return false;
}
for (int i = 0; i < array1.Length; i++)
{
if (array1[i] != array2[i])
{
return false;
}
}
return true;
}
Always use a new salt per password. Salts do not have to be kept secret and can be stored alongside the hash itself.
What blowdart said, but with a little less code. Use Linq or CopyTo
to concatenate arrays.
public static byte[] Hash(string value, byte[] salt)
{
return Hash(Encoding.UTF8.GetBytes(value), salt);
}
public static byte[] Hash(byte[] value, byte[] salt)
{
byte[] saltedValue = value.Concat(salt).ToArray();
// Alternatively use CopyTo.
//var saltedValue = new byte[value.Length + salt.Length];
//value.CopyTo(saltedValue, 0);
//salt.CopyTo(saltedValue, value.Length);
return new SHA256Managed().ComputeHash(saltedValue);
}
Linq has an easy way to compare your byte arrays too.
public bool ConfirmPassword(string password)
{
byte[] passwordHash = Hash(password, _passwordSalt);
return _passwordHash.SequenceEqual(passwordHash);
}
Before implementing any of this however, check out this post. For password hashing you may want a slow hash algorithm, not a fast one.
To that end there is the Rfc2898DeriveBytes
class which is slow (and can be made slower), and may answer the second part of the original question in that it can take a password and salt and return a hash. See this question for more information. Note, Stack Exchange is using Rfc2898DeriveBytes
for password hashing (source code here).
I've been reading that hashing functions like SHA256 weren't really intended for use with storing passwords: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes
Instead adaptive key derivation functions like PBKDF2, bcrypt or scrypt were. Here is a PBKDF2 based one that Microsoft wrote for PasswordHasher in their Microsoft.AspNet.Identity library:
/* =======================
* HASHED PASSWORD FORMATS
* =======================
*
* Version 3:
* PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
* Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
* (All UInt32s are stored big-endian.)
*/
public string HashPassword(string password)
{
var prf = KeyDerivationPrf.HMACSHA256;
var rng = RandomNumberGenerator.Create();
const int iterCount = 10000;
const int saltSize = 128 / 8;
const int numBytesRequested = 256 / 8;
// Produce a version 3 (see comment above) text hash.
var salt = new byte[saltSize];
rng.GetBytes(salt);
var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);
var outputBytes = new byte[13 + salt.Length + subkey.Length];
outputBytes[0] = 0x01; // format marker
WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
WriteNetworkByteOrder(outputBytes, 5, iterCount);
WriteNetworkByteOrder(outputBytes, 9, saltSize);
Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
return Convert.ToBase64String(outputBytes);
}
public bool VerifyHashedPassword(string hashedPassword, string providedPassword)
{
var decodedHashedPassword = Convert.FromBase64String(hashedPassword);
// Wrong version
if (decodedHashedPassword[0] != 0x01)
return false;
// Read header information
var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);
// Read the salt: must be >= 128 bits
if (saltLength < 128 / 8)
{
return false;
}
var salt = new byte[saltLength];
Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);
// Read the subkey (the rest of the payload): must be >= 128 bits
var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
if (subkeyLength < 128 / 8)
{
return false;
}
var expectedSubkey = new byte[subkeyLength];
Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);
// Hash the incoming password and verify it
var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength);
return actualSubkey.SequenceEqual(expectedSubkey);
}
private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
{
buffer[offset + 0] = (byte)(value >> 24);
buffer[offset + 1] = (byte)(value >> 16);
buffer[offset + 2] = (byte)(value >> 8);
buffer[offset + 3] = (byte)(value >> 0);
}
private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
return ((uint)(buffer[offset + 0]) << 24)
| ((uint)(buffer[offset + 1]) << 16)
| ((uint)(buffer[offset + 2]) << 8)
| ((uint)(buffer[offset + 3]));
}
Note this requires Microsoft.AspNetCore.Cryptography.KeyDerivation nuget package installed which requires .NET Standard 2.0 (.NET 4.6.1 or higher). For earlier versions of .NET see the Crypto class from Microsoft's System.Web.Helpers library.
Update Nov 2015
Updated answer to use an implementation from a different Microsoft library which uses PBKDF2-HMAC-SHA256 hashing instead of PBKDF2-HMAC-SHA1 (note PBKDF2-HMAC-SHA1 is still secure if iterCount is high enough). You can check out the source the simplified code was copied from as it actually handles validating and upgrading hashes implemented from previous answer, useful if you need to increase iterCount in the future.
Salt is used to add an extra level of complexity to the hash, to make it harder to brute-force crack.
From an article on Sitepoint:
A hacker can still perform what's called a dictionary attack. Malicious parties may make a dictionary attack by taking, for instance, 100,000 passwords that they know people use frequently (e.g. city names, sports teams, etc.), hash them, and then compare each entry in the dictionary against each row in the database table. If the hackers find a match, bingo! They have your password. To solve this problem, however, we need only salt the hash.
To salt a hash, we simply come up with a random-looking string of text, concatenate it with the password supplied by the user, then hash both the randomly generated string and password together as one value. We then save both the hash and the salt as separate fields within the Users table.
In this scenario, not only would a hacker need to guess the password, they'd have to guess the salt as well. Adding salt to the clear text improves security: now, if a hacker tries a dictionary attack, he must hash his 100,000 entries with the salt of every user row. Although it's still possible, the chances of hacking success diminish radically.
There is no method automatically doing this in .NET, so you'll have go with the solution above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With