A NET Cryptography Primer, Part Two

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:

  1. Take a user identifier and a password from the user
  2. Create a salt of random characters
  3. Concatenate the given password with the generated salt (in a standardized way)
  4. Use a cryptographic hash algorithm to encrypt this string
  5. Store the encrypted string, the hash, and the unencrypted user identifier

 

To verify a password, we must:

  1. Take a user identifier and a password from the user
  2. Look up the encrypted string and hash using the given user identifier
  3. Combine the retrieved hash and the given password in the standard way
  4. Use the cryptographic hash algorithm to encrypt this string
  5. Compare the newly encrypted string with the one retrieved from the database
  6. 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:

<span style="font-weight: bold; color: #2f2fff">private</span><span> </span><span style="font-weight: bold; color: #2f2fff">static</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">CreateSalt</span><span>(</span><span style="font-weight: bold; color: #2f2fff">int</span><span> </span><span style="color: navy">saltStrength</span><span>)</span>

<span>{</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">byte</span><span>[] </span><span style="color: navy">temp</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="font-weight: bold; color: #2f2fff">byte</span><span>[</span><span style="color: navy">saltStrength</span><span>];</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">RNGCryptoServiceProvider</span><span style="color: fuchsia">.</span><span style="color: navy">Create</span><span>()</span><span style="color: fuchsia">.</span><span style="color: navy">GetBytes</span><span>(</span><span style="color: navy">temp</span><span>);</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">return</span><span> </span><span style="color: #2b91af">Encoding</span><span style="color: fuchsia">.</span><span style="color: navy">Default<span style="color: fuchsia">.</span>GetString</span><span>(</span><span style="color: navy">bytes</span><span>);</span>

<span>}</span>

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.

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">const</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">SHA1HashName</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="background: #f3f8f3; color: green">&quot;SHA1&quot;</span><span>;</span>

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">const</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">SHA256HashName</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="background: #f3f8f3; color: green">&quot;SHA256&quot;</span><span>;</span>

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">const</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">SHA512HashName</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="background: #f3f8f3; color: green">&quot;SHA512&quot;</span><span>;</span>

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">const</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">MD5HashName</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="background: #f3f8f3; color: green">&quot;MD5&quot;</span><span>;</span>

&nbsp;

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">static</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">HashPassword</span><span>(</span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">hashName</span><span>, </span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">password</span><span>, </span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">out</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">salt</span><span>, </span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">int</span><span> </span><span style="color: navy">saltStrength</span><span>)</span>

<span>{</span>

<span style="color: #2b91af">    HashAlgorithm</span><span> </span><span style="color: navy">ha</span><span> </span><span style="color: fuchsia">=</span><span> </span>

<span style="color: #2b91af">        HashAlgorithm</span><span style="color: fuchsia">.</span><span style="color: navy">Create</span><span>(</span><span style="color: navy">hashName</span><span>);</span>

<span style="font-weight: bold; color: #2f2fff">    if</span><span> (</span><span style="color: navy">ha</span><span> </span><span style="color: fuchsia">==</span><span> </span><span style="font-weight: bold; color: #2f2fff">null</span><span>)</span>

<span style="font-weight: bold; color: #2f2fff">        throw</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">InvalidOperationException</span><span>(</span>

<span style="background: #f3f8f3; color: green">            &quot;Cannot map name &quot;</span><span> </span><span style="color: fuchsia">+</span><span> </span><span style="color: navy">hashName</span><span> </span><span style="color: fuchsia">+</span><span> </span>

<span style="background: #f3f8f3; color: green">            &quot; to a hash algorithm.&quot;</span><span>);</span>

&nbsp;

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: navy">salt</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: navy">CreateSalt</span><span>(</span><span style="color: navy">saltStrength</span><span>);</span>

<span style="font-weight: bold; color: #2f2fff">    byte</span><span>[] </span><span style="color: navy">temp</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: #2b91af">Encoding</span><span style="color: fuchsia">.</span><span style="color: navy">Default</span><span style="color: fuchsia">.</span><span style="color: navy">GetBytes</span><span>(</span>

<span style="color: navy">                             password</span><span> </span><span style="color: fuchsia">+</span><span> </span><span style="color: navy">salt</span><span>);</span>

<span style="color: navy">    temp</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: navy">ha</span><span style="color: fuchsia">.</span><span style="color: navy">ComputeHash</span><span>(</span><span style="color: navy">temp</span><span>);</span>

&nbsp;

<span style="font-weight: bold; color: #2f2fff">    return </span><span style="color: #2b91af">Convert</span><span style="color: fuchsia">.</span><span style="color: navy">ToBase64String</span><span>(</span><span style="color: navy">temp</span><span>);</span>

<span>}</span>

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.

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">static</span><span> </span><span style="font-weight: bold; color: #2f2fff">bool</span><span> </span><span style="color: navy">Verify</span><span>(</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">hashName</span><span>, </span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">password</span><span>, </span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">encryptedPassword</span><span>,</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">salt</span><span>)</span>

<span>{</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">HashAlgorithm</span><span> </span><span style="color: navy">ha</span><span> </span><span style="color: fuchsia">=</span><span> </span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">HashAlgorithm</span><span style="color: fuchsia">.</span><span style="color: navy">Create</span><span>(</span><span style="color: navy">hashName</span><span>);</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">if</span><span> (</span><span style="color: navy">ha</span><span> </span><span style="color: fuchsia">==</span><span> </span><span style="font-weight: bold; color: #2f2fff">null</span><span>)</span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">throw</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">InvalidOperationException</span><span>(</span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="background: #f3f8f3; color: green">&quot;Cannot map name &quot;</span><span> </span><span style="color: fuchsia">+</span><span> </span><span style="color: navy">hashName</span><span> </span><span style="color: fuchsia">+</span><span> </span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="background: #f3f8f3; color: green">&quot; to a hash algorithm.&quot;</span><span>);</span>

&nbsp;

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">byte</span><span>[] </span><span style="color: navy">temp</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: #2b91af">Encoding</span><span style="color: fuchsia">.</span><span style="color: navy">Default</span><span style="color: fuchsia">.</span><span style="color: navy">GetBytes</span><span>(</span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: navy">password</span><span> </span><span style="color: fuchsia">+</span><span> </span><span style="color: navy">salt</span><span>);</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: navy">temp</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: navy">ha</span><span style="color: fuchsia">.</span><span style="color: navy">ComputeHash</span><span>(</span><span style="color: navy">temp</span><span>);</span>

&nbsp;

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">toTest</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: #2b91af">Convert</span><span style="color: fuchsia">.</span><span style="color: navy">ToBase64String</span><span>(</span><span style="color: navy">temp</span><span>);</span>

&nbsp;

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">return</span><span> </span><span style="color: navy">encryptedPassword</span><span style="color: fuchsia">.</span><span style="color: navy">Equals</span><span>(</span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: navy">toTest</span><span>, </span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">StringComparison</span><span style="color: fuchsia">.</span><span style="color: navy">Ordinal</span><span>);</span>

<span>}</span>

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!

kick it on DotNetKicks.com

4 Responses to A NET Cryptography Primer, Part Two

  1. vonPryz says:

    Nice intro-level text, but the terminology is not quite there. A hashed string is not encrypted. Encryption indicates two-way transform, that is, a reversable one. Hash isn’t.

    The approach of using custom salt for each password is interesting one. It adds an extra layer of obscurity, but I wonder if it really provides additional protection. If the attacker already has a copy of an user database, you have failed to secure the system anyway. In such cases, the hashing is more damage control than security.

    Salt doesn’t need to be random. Its main benefit is to protect against situations in which user "Joe" has password "joe" or some other obvious one. After salting with, say "SaLtTheFrIeS", Joe’s password will become "joeSaLtTheFrIeS", which will have very much different hash than the original one.

    Oh, and a 8 char password out of 48 char alphabet has 48 * 48 * … * 48 = 48^8 = 28,17 * 10^12 possible alternatives.

  2. Will says:

    Thanks for the additional info. Yes, if your database has been compromised, you’re up shit creek. But there are situations where people can get ahold of your passwords but can’t modify the data on your server. In fact, this just recently happened with BlogEngine.NET: http://dannydouglass.com/post/2008/04/BlogEngine-and-the-JavaScript-HttpHandler-Serious-Security-Issue.aspx

    You could get a copy of the user data, but could not modify it on the server. In this case, the passwords were in plaintext (omgwtflol). But if they were hashed, at least that would have given them a slightly greater level of security.

    A hacker in this case would have been forced to use a dictionary attack to determine the original password. A salt would have forced the hacker to generate their tables from scratch rather than using predefined tables, making their job exponentially more difficult.

  3. teedyay says:

    There are two weaknesses in your method.

    [b]Firstly:[/b]
    If the hacker gets a snapshot of your user table, he knows the hashed password and the salt for every user.

    If he guesses you use SHA512(Password + Salt) (as most people do), then you’re open to a dictionary hack again.

    To combat this, use an application-level salt as well. This is hard-coded in the source code of your application. Use SHA512(Password + Salt + "SomeConstantUnguessableString"), and you’re safe again.

    [b]Secondly:[/b]
    If a hacker can (momentarily) alter your user table, he can copy the salt and hash from his user record to other user records. As he knows his own password, he can now log in as those other users.

    To prevent this, also salt the hash with something unique about the user – maybe their username, email address or the primary key of the user record.

    [b]In conclusion:[/b]
    As far as I’m aware, the best way to store passwords securely is:

    [b]Hash(UserId + Password + UserSalt + ApplicationSalt)[/b]

    This is only open to a dictionary hack if the hacker knows the ApplicationSalt (i.e. has (momentarily) gained access to your source code), and even then he’ll have to create a new dictionary for every record in your database.

  4. Will says:

    1) Its not [i]my[/i] method; its an industry standard method. For example, its how ASP.NET authentication stores passwords.

    Now, I never said it was perfect. But even if you know the algorithm used AND all the salts, it still would take a VERY long time for an individual to mount a dictionary attack. For more info check out this post: http://www.codinghorror.com/blog/archives/000949.html

    Also, a secret that is hard coded in your application is NOT a secret.

    2) If a hacker has write access to your database you have already lost the game. No amount of super secret double reverse salt this that and the other thing will matter.

    BTW, I looked it up… A random salt of 16bits would require over a quadrillion hashes to be generated for a lookup table. I think it might be okay to skip the super secret encoded-in-my-source extra hash. Hell, let’s go crazy and make the salt 64 bits!

Comments are closed.