By now you should better understand managed vs. unmanaged classes in System.Security.Cryptography namespace and how to move from strings to byte arrays and back again.
This post covers storing passwords in a secure manner using salty hash. Yum.
Cryptographic Hashing
A paragraph or two about cryptographic hashes before covering the secure storage of passwords. We all know what hash functions are; every object in the .NET platform contains the method GetHashCode(), which returns an integer representation of an object. A cryptographic hash is an algorithm that takes a string of any length and returns a fixed size, highly random yet deterministic string. For a cryptographic hash to be considered secure, it must produce the same output for the same string, yet vastly different outputs for two very similar strings. This makes it easy to verify yet hard to crack.
Probably the most important thing to understand is that secure cryptographic hashes are very difficult, if not impossible, to reverse. That means, once data passes through the hash algorithm, you cannot reverse the hash function and get the original data back out. Obviously, this limits the scenarios where cryptographic hashes are useful. But, because cryptographic hashes are simple, secure, and don't require any kind of "shared secret" to be hidden in the source code, they are widely used. With the application of the proper patterns, they can be extremely useful for keeping data secure.
Hash Without Salt is Bland
One of the weaknesses of cryptographic hashing is that it is deterministic, meaning that string A will always produce hash B when using the same hash algorithm. This allows attackers an avenue to crack a hash: Guess the original string, A. This weakness is very evident in password storage. A hacker, with a list of all password hashes in a database, can gain access to accounts using a lookup table. This table has, for a given hash algorithm, pre-calculated hashes for certain combinations of characters. Simple ones contain hashes for words in the dictionary. That seems a bit complex and large, but considering a password of length 8 consisting of the 48 characters readily available to you on your keyboard has approximately 377 million combinations (I hope I got my stats correct), its actually the simplest thing to do.
To combat this, a random string of characters is added to the password, either to the front or the back. This increases the overall length of the password, making random character lookup tables exponentially more difficult to use. It also breaks dictionary attacks as, even though the salt is eventually stored in the database along with the hash, you must re-create the dictionary lookup table with each stored salt, a monumental task in and of itself.
I Thought You Were Emphasizing Code
Right. Now we know what to do, we can generate some pseudocode to identify our password hashing code's inputs and outputs. To store passwords securely using a cryptographic hash, we must:
- Take a user identifier and a password from the user
- Create a salt of random characters
- Concatenate the given password with the generated salt (in a standardized way)
- Use a cryptographic hash algorithm to encrypt this string
- Store the encrypted string, the hash, and the unencrypted user identifier
To verify a password, we must:
- Take a user identifier and a password from the user
- Look up the encrypted string and hash using the given user identifier
- Combine the retrieved hash and the given password in the standard way
- Use the cryptographic hash algorithm to encrypt this string
- Compare the newly encrypted string with the one retrieved from the database
- If they match, return positive, otherwise return negative
The Salt
Salts must be random. More random that System.Random can provide. The .NET platform provides a means to create a random enough salt via RNGCryptoServiceProvider. Here's the code:
private static string CreateSalt(int saltStrength)
{
byte[] temp = new byte[saltStrength];
RNGCryptoServiceProvider.Create().GetBytes(temp);
return Encoding.Default.GetString(bytes);
}
because I'm concatenating the salt to a string, I'm converting the byte array of random bytes into a Unicode string using the system's default code page. Be careful in this step; if you convert the array into a hex string, for example, you significantly reduce the randomness of the salt (255 possibilities for n characters vs. 16 possibilities for n characters). Keeping the salt big and encoding it as Unicode (or not encoding it at all) is the best way to go.
The Hash Method
Now that we can generate our salt, we can continue with the hash method. The only weird thing about this method is that it takes the name of the hash algorithm implementation as a string. I've included statics for the names of the most common hash algorithms.
public const string SHA1HashName = "SHA1";
public const string SHA256HashName = "SHA256";
public const string SHA512HashName = "SHA512";
public const string MD5HashName = "MD5";
public static string HashPassword(string hashName,
string password,
out string salt,
int saltStrength)
{
HashAlgorithm ha =
HashAlgorithm.Create(hashName);
if (ha == null)
throw new InvalidOperationException(
"Cannot map name " + hashName +
" to a hash algorithm.");
salt = CreateSalt(saltStrength);
byte[] temp = Encoding.Default.GetBytes(
password + salt);
temp = ha.ComputeHash(temp);
return Convert.ToBase64String(temp);
}
Pretty simple and straight forward. This method converts the encrypted byte array into a base64 string for ease of storage. You could, of course, store it as binary data or encode it differently. At this point, it mostly doesn't matter; all you're doing is substituting length for complexity, resulting in equally secure crypts.
Note the salt argument is an out parameter. This is because not only will you store the return value of this method, but also the salt generated. Not as elegant as a F# tuple, but it works.
The Verify Method
When a user comes to us and claims to be someone known to the system, they give us a user identifier (aka user name) and password as proof. We can use the following method to compare the two. Please note, prior to this method being called, you must retrieve the salt and encrypted password from your repository (file, database, etc) for the given user identifier.
public static bool Verify(
string hashName,
string password,
string encryptedPassword,
string salt)
{
HashAlgorithm ha =
HashAlgorithm.Create(hashName);
if (ha == null)
throw new InvalidOperationException(
"Cannot map name " + hashName +
" to a hash algorithm.");
byte[] temp = Encoding.Default.GetBytes(
password + salt);
temp = ha.ComputeHash(temp);
string toTest = Convert.ToBase64String(temp);
return encryptedPassword.Equals(
toTest,
StringComparison.Ordinal);
}
This method is very similar to the first, except that we return the result of an Ordinal Equals comparison. Note that we are converting to a base64 string, just like in the encrypt method. Also note the comparison is ordinal. This is because Convert.ToBase64String returns a string encoding that includes only simple ASCII characters, and is not affected by the language or culture that this code is running under. I think. I can't guarantee this will pass the Turkey test. If you're expecting your hashes to run in different cultures, a little more investigation into the encoding is warranted.
Summary
Hashing is a one way business. And hash is not all that good without a healthy dose of salt. But a nice salty hash is great for encrypting and storing passwords without having to hard-code a password into your source code, which is dumb.
Part Three Preview
In part three, we will be covering symmetric, or "shared secret", encryption. Every time a developer hard-codes a password in their source code, God kills a kitten. Please, think of the kittens!