Password Hashing using Rfc2898DeriveBytes

By: Chris Dunn

With the recent data security breaches, we as developers need to make sure we are doing our best to secure the application data the best we can.  One very obvious problem is passwords and other sensitive data stored in plain-text in a user's database record on the server.

For data that you must maintain intact, like perhaps a social security number or similar identifier, you need to encrypt the data completely.  It's always a good idea to take a long hard look at what data you must maintain.  The data you choose to store, is now your responsibility to secure.

Storing Passwords

Some data, like passwords, don't need to be stored in their original form.  Since most people reuse passwords, if the data is compromised, we risk not only our users accounts with our organization, but also banking, email and social media.  While we can't save a user from themselves, we don't need to help them fail.

With passwords we can simply hash the data.  Hashing stores a computed value based on the data.  The same data hashed, using the same algorithm, returns the same value.  So we can use this to validate a password by hashing an inputted password against a hashed value of the original password.  So now we never need to store the plain text password.

MD5 Hash

A simple MD5 hash is better than plain text, but not a whole lot better.  If we hash "password" with MD5, we get "5f4dcc3b5aa765d61d8327deb882cf99".  Now go on Google and search for that hash.  Guess what, you'll find a lot of results.  So if your data is compromised and you use a simple hash, a simple program can retrieve all  common passwords even when hashed.

Salt and Rainbow Tables

Using a salt on the hash can help to prevent pre-computational attacks and generation of Rainbow Tables . https://en.wikipedia.org/wiki/Rainbow_table.  Basically we're trying to make it hard on the attacker to use the data if it were compromised. 

For hashing we want to use Rfc2898DeriveBytes class to generate your hash utilizing a random salt, and fixed or random iterations to derive the key.  Including the salt, and varying iterations, we make the hash value harder to uncover.  See the full details on the class here.

Solution

Now that we know some of what we're up against, let's put together a solution that at least makes our data less enticing to hackers, compared with the next guy. 

First we're going to generate a random salt, in this case 24 bytes, using the RNGCryptoServiceProvider.

   var salt = new byte[24];
new RNGCryptoServiceProvider().GetBytes(salt);

Using that salt, we're going to generate the hash using Rfc2898DeriveBytes.  The iterations is defaulted to 1000 but can be changed per your needs.

 var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
            byte[] hash = pbkdf2.GetBytes(24);

Finally, we're going to combine the salt, # of iterations and hash value and delimit those values by a pipe.  We can then store this combined value. 

             Convert.ToBase64String(salt) + "|" + iterations + "|" +
                Convert.ToBase64String(hash);

So, the combined value for the value "mypassword" using 975 iterations and a salt random salt would be:

ZQIYIY4PMDWdUbMPn2jIhIQs8JyDX4va|975|XRtt9NEv7PW0d1f/uzjw1c35vZdW5sXd

This is the value we would store in the database.  To validate a user password, we generate a hash from the password value, based on the salt (first value) and iterations (second value) and compare it to the stored hash (last value). 

With that, we have stored all the necessary information needed to secure our application and user login, and we never store the actual password.  We've also made it very expensive for the hackers to even try to figure out what a single password could be. 

To make it easier on you, and me, I've created a utility class to Generate and Validate a generated hash.  Here is the complete PasswordHasher utility class you can use to create your own.

    public class PasswordHasher
    {
        ///
        /// Return a string delimited with random salt, #iterations and hashed password
        /// can be store in database for validation.
        ///
        ///
        ///
        ///
        public string Generate(string password, int iterations = 1000)
        {
            //generate a random salt for hashing
            var salt = new byte[24];
            new RNGCryptoServiceProvider().GetBytes(salt);

            //hash password given salt and iterations (default to 1000)
            //iterations provide difficulty when cracking
            var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
            byte[] hash = pbkdf2.GetBytes(24);
            
            //return delimited string with salt | #iterations | hash
            return Convert.ToBase64String(salt) + "|" + iterations + "|" +
                Convert.ToBase64String(hash);
            
        }

        ///
        /// Returns true of hash of test password matches hashed password within origDelimHash
        ///
        ///
        ///
        /// 
        public bool IsValid(string testPassword, string origDelimHash)
        {
            //extract original values from delimited hash text
            var origHashedParts = origDelimHash.Split('|');
            var origSalt = Convert.FromBase64String(origHashedParts[0]);
            var origIterations = Int32.Parse(origHashedParts[1]);
            var origHash = origHashedParts[2];

            //generate hash from test password and original salt and iterations
            var pbkdf2 = new Rfc2898DeriveBytes(testPassword, origSalt, origIterations);
            byte[] testHash = pbkdf2.GetBytes(24);
            
            //if hash values match then return success
            if (Convert.ToBase64String(testHash) == origHash)
                return true;
            
            //no match return false
            return false;

        }
    }
Tags: c# cryptography hashing

Copyright 2023 Cidean, LLC. All rights reserved.

Proudly running Umbraco 7. This site is responsive with the help of Foundation 5.