Click here to Skip to main content
15,888,350 members
Articles / Security

A Recipe For Password Security

Rate me:
Please Sign up or sign in to vote.
4.62/5 (7 votes)
20 Sep 2014CPOL9 min read 14.7K   17   13
Using strong cryptography with entropy to secure user passwords.

Introduction

Several months ago, I helped architect a password security scheme for a client. During that process, I learned quite a bit about how to encrypt passwords in a secure fashion.

Encryption vs. Hashing

Most developers have heard the term "encryption", which means that data is encoded in such a way that it is not human-readable. But in the context of password security, the word “encryption” implies that the encoding can be decoded, that is, it’s a “two-way” encryption. While it may be advantageous to decode a user’s password, especially in situations where they have forgotten it, it opens up a security hole. Simply put, if someone attacking your security implementation can guess the algorithm and parameters used to encrypt passwords, they can then decrypt all the passwords in your system! At this point, you have the equivalent of passwords stored in your system in plaintext – not an excellent approach.

A much more secure method for storing encrypted passwords is to use a cryptographically secure hash1. A “hash” is an algorithm that will take a block of data and from that information, generate a value such that if any of the data is changed, the hashed value will change as well. The block of data is generally called a “message” and the hashed value is called a “digest”. What is valuable about cryptographic hashes with regard to password security is that they are “one-way”, that is once the password has been hashed, it cannot be decrypted back to its original plaintext form. This eliminates the security vulnerability that exists with two way encryption.

By now, I’m sure some of you have thought, “Great, if I have this hashed value, how do I validate that against the plaintext password typed in by the user?” The answer is, you don’t. When the user types in their password, you hash the value that they entered using the same hash algorithm. You then compare that hashed value with the hashed password stored in your system. If they match, then the user is authenticated.

Adding Some Salt

So we now have a process for storing passwords in our system in a secure form that cannot be decrypted, thus closing the door that allows attackers access to all the passwords stored in the system. But determined attackers are not so easily thwarted. They will use a rainbow of methods to gain access to your systems, which segues (in a ham-handed fashion) into the next topic, rainbow tables.

Since they can no longer decrypt your passwords, attackers will try the next best thing. They’ll take a large list of common words and passwords and hash them using some of the well-known standard algorithms. They’ll then compare this list of hashed words to your password list. Any matches will immediately indicate a successful password search. Given users’ penchant for commonly used passwords, the chances are good that the attacker will end up with quite a few successes.

The generally accepted practice for prohibiting this practice is to use a “password salt”2. A salt value is just a randomly generated value that is added to the user’s password before hashing. The salt value is then stored with the user’s hashed password so that the authentication method can use it to hash a password entered by the user.

Now I’m sure some of you are wondering how this prevents rainbow table attacks if the salt value is easily accessible. What the salt value does is require the attacker to regenerate all the values in their rainbow table using the specified salt value. Even if they have a match, it will only work for the one user for which that particular salt value was used. While it doesn’t prevent a successful attack, it certainly limits it to one success and makes it very slow and cumbersome for the attacker to make additional attempts on other passwords.

Needs Some Pepper

So how can we make it even more difficult for the determined attacker? Well, we can add a “secret salt” value not stored in the database to the password before we hash it. This value would be well known to the system so that it can reproduce it as necessary for authentication but would not be stored in the database. This type of value is commonly known as a “pepper” value. The fact that it is not published or stored makes it even more difficult for an attacker to guess what the plaintext value was before hashing. Unless they have access to the source code for generating the pepper value, they may never be able to generate a successful rainbow table.

Simmer Slowly

So it seems like we’ve covered all the bases. But we can’t forget about Moore’s Law3. As CPUs and GPUs get faster and faster, it becomes easier to generate multiple rainbow tables so that an attacker can take many guesses at an encrypted password list. What’s a poor, security-minded developer to do?

Well, how about we purposely slow them down?

There are several well known cryptographic hash algorithms4, such as the Message Digest derivatives (MD2, MD4, MD5) and the Secure Hash Algorithms from the National Security Agency (SHA-1, SHA-256) but many of these were designed to work quickly. In some cases, like MD5, the algorithm is considered “cryptographically broken”5. What we really need is a hash algorithm that can be adapted so that it is slow enough to discourage the generation of multiple rainbow tables but fast enough to hash a password quickly after a user types it in for authentication.

Enter bcrypt6. Bcrypt is a hashing function based on the well-regarded Blowfish encryption algorithm that includes an iteration count to make it process more slowly. Even if the attacker knows that bcrypt is the algorithm in use if a properly selected iteration count is employed, it renders the generation of rainbow tables very expensive. Furthermore, the iteration count is stored in the hashed result value so it’s forward compatible; that is as computing power continues to increase the iteration count can be increased and applied to existing password hashes so that the generation of rainbow tables continues to be expensive.

Taste While Cooking

So we've got a recipe for encrypting passwords, what does this look like when you're cooking it? Here's some sample code to illustrate, as per request by CP user George Jonsson.

First, we'll need a table in our database to store user information. I use SQL Server predominantly, so here's a table creation script for that database management system.

SQL
IF OBJECT_ID( 'Users' ) IS NOT NULL
	DROP TABLE Users
GO

CREATE TABLE Users(
	UserId			int				IDENTITY NOT NULL,
	Username		varchar( 50 )	NOT NULL UNIQUE,
	Password		varchar( 128 )		NULL,
	CONSTRAINT PKUsers
		PRIMARY KEY( UserId )
)
GO

You'll notice that I left the password column nullable. The reason will become clear in the code, where I use the user ID for part of the pepper value, but I can't get tthis value until I insert a row.

Then we'll need a POCO object to model the data...

C#
using System;

namespace BcryptExample.Model
{
    class User
    {
        public int UserId { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
    }
}

...and a controller to handle the CRUD plumbing.

C#
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using Dapper;
using log4net;

namespace BcryptExample.Controllers
{
    class UserController
    {
        private ILog logger = LogManager.GetLogger( "BcryptExample" );
        private readonly string connectionString;

        public UserController()
        {
            connectionString = ConfigurationManager.ConnectionStrings[ "ExamplesDb" ].ConnectionString;
        }

        public Model.User AddUser( Model.User user )
        {
            try {
                using( IDbConnection conn = new SqlConnection( connectionString ) ) {
                    conn.Open();

                    using( IDbTransaction trans = conn.BeginTransaction() ) {
                        try {
                            user.UserId = conn.Query<int>( "INSERT INTO Users( Username ) 
                                                      OUTPUT inserted.UserId 
                                                      VALUES( @UserName ) ",
                                                       new { Username = user.Username },
                                                       trans,
                                                       commandType : CommandType.Text ).FirstOrDefault();

                            string cryptPw = PasswordHash.HashPassword( user.Password, 
                                                                        user.Username, 
                                                                        user.UserId );
                                
                            user.Password = cryptPw;

                            conn.Execute( "UPDATE Users SET Password = @Password WHERE Userid = @UserId",
                                          new { Password = cryptPw, user.UserId }, 
                                          trans );

                            trans.Commit();
                        }
                        catch( Exception ex ) {
                            trans.Rollback();
                            logger.Error( "Error in UserController.AddUser transaction.", ex );
                        }
                    }

                    conn.Close();
                }
            }
            catch( Exception ex ) {
                logger.Error( "Error in UserController.AddUser.", ex );
                throw;
            }

            return user;
        }

        public Model.User GetUser( string username )
        {
            Model.User user = null;

            try {
                using( IDbConnection conn = new SqlConnection( connectionString ) ) {
                    conn.Open();

                    user = conn.Query<model.user>( "SELECT UserId, Username, Password 
                                                         FROM Users 
                                                         WHERE Username = @Username",
                                                    new { Username = username },
                                                    commandType: CommandType.Text ).FirstOrDefault();
                    conn.Close();
                }
            }
            catch( Exception ex ) {
                logger.Error( "Error in UserController.GetUser.", ex );
                throw;
            }

            return user;
        }
    }
}
</model.user></int>

There are a few items to note in the controller code.

  • I used Marc Gravell and Sam Saffron's superb Micro-ORM, Dapper for database usage. I find it less intrusive than EF and other heavy ORMs, but if that's what you like than feel free to use that;
  • I used log4net for output logging. Again, if this is not your preference feel free to swap out;
  • I loaded both the previously mentioned libraries using NuGet, also highly recommended;

The AddUser method inserts a row into the user table without a password and uses the SQL Server OUTPUT clause to return the new User ID. This ID, along with the user name, are used to create a pepper value, using a process that is shown below. However, it is not necessarily recommended that you use these values for a cryptography pepper. You would generally want the pepper value to be immutable, and username is something that, while not very volatile, is most likely not immutable. It is used here strictly for purposes of illustration.

This is the code for the class where password hashing is performed.

C#
using System;
using System.Linq;

namespace BcryptExample
{
    class PasswordHash
    {
        private const int WORK_FACTOR = 10;
        private const int PEPPER_KEY = 7591232;

        public static string HashPassword( string password, string username, int userId )
        {
            string temp = PepperPassword( password, username, userId );
            return BCrypt.Net.BCrypt.HashPassword( temp, WORK_FACTOR );
        }

        public static bool VerifyPassword( string password, string username, int userId, string cryptText )
        {
            string temp = PepperPassword( password, username, userId );
            return BCrypt.Net.BCrypt.Verify( temp, cryptText );
        }

        private static string PepperPassword( string password, string username, int userId )
        {
            string temp = String.Format( "{0}{1}{2:0000000}", password, username, userId );
            return Shuffle( temp, PEPPER_KEY );
        }

        private static string Shuffle( string source, int randKey )
        {
            // Simple Knuth-Fisher-Yates deterministic shuffle of a string
            // as adapted from pseudo-code at http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
            char[] chars = source.ToArray();
            Random rand = new Random( randKey );    // to insure consistent randomizing (huh?!?)
            char swap;

            for( int charIdx = chars.Length - 1; charIdx > 0; charIdx-- ) {
                int n = rand.Next( charIdx );
                swap = chars[ n ];
                chars[ n ] = chars[ charIdx ];
                chars[ charIdx ] = swap;
            }

            return new string( chars );
        }
    }
}

The notable items in this code snippet are:

  • I used the open source BCrypt.Net library for secure hashing with a work factor, as described earlier. If installing from NuGet this library is the one that says "official";
  • The password pepper generation takes three inputs: the plaintext password, the username, and the user ID, concatenates them into a string, which then has its component characters shuffled in a deterministic fashion so that we can recreate it for validation;
  • BCrypt.Net provides its own handy validation method that you pass the plain text and the stored hashed password. It will then take the salt value, which is included in the encrpyted data, and the work factor, also included in the encrypted data, and hash the plaintext password to compare;
  • The Shuffle method uses an algorithm created by Fisher and Yates that was published by Knuth that provides good random distribution. I used a random seed to insure that the shuffle is deterministic, meaning that it will always return the same value given the same inputs.

The BCrypt library will turn the plain text password, for example "password99", into something like "$2a$10$InkLvHP9ZYIFl5zBNt13uuYdU8GY6eUZRsYWybHzmsHiGG4azBSj6". This is a base 64 encoded string, the first segments of which indicate the version and work factor in that order. The salt value is also embedded in the string, and the BCrypt library knows how to extract it at verification time. See this StackOverflow article (http://stackoverflow.com/a/6833165/49954) for an explanation of the portions of a BCrypt hash.

And here's a small sample program that will illustrate the usage:

C#
using System;

namespace BcryptExample
{
    class Program
    {
        static void Main( string[] args )
        {
            Controllers.UserController controller = new Controllers.UserController();

            Model.User user = controller.GetUser( "newuser" );
            if( user == null ) {
                Model.User newUser = new Model.User { Username = "newuser", Password = "password99" };
                user = controller.AddUser( newUser );
            }

            Console.Write( "Enter password for newuser: " );
            string password = Console.ReadLine();

            if( PasswordHash.VerifyPassword( password, user.Username, user.UserId, user.Password ) ) {
                Console.WriteLine( "You entered an valid password" );
            }
            else {
                Console.WriteLine( "You entered an invalid password" );
            }
        }
    }
}

The code at the top of the routine attempts to retrieve the user's information from the database. If it's not found, then create it.

The code at the bottom of the routine prompts for the password, which is then validated using the recipe outline in the article.

A Spicy Meatball

So by using a combination of the right spices (salt and pepper) and the proper cook time (iterations), we can end up with an excellently prepared plate of hash. It’s not perfect - no security approach ever is - but we can certainly make our systems less vulnerable to the point where an attacker will look for victims that are less well-protected. And that’s all we can really hope for, that they look somewhere else.

Additional References

Coding Horror: You're Probably Storing Passwords Incorrectly


1. http://en.wikipedia.org/wiki/Cryptographic_hash_function
2. http://en.wikipedia.org/wiki/Salt_(cryptography)
3. http://en.wikipedia.org/wiki/Moores_law
4. http://en.wikipedia.org/wiki/Cryptographic_hash_function#Cryptographic_hash_algorithms
5. http://en.wikipedia.org/wiki/MD5
6. http://en.wikipedia.org/wiki/Bcrypt

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO RAMSoft Solutions, Inc
United States United States
Bob McGowan is the founder and CEO of RAMSoft Solutions and has led the company since its inception in 1993. He has over 30 years of experience managing projects and developing applications software for microcomputers. He has managed multi-million dollar software development projects for many Fortune 100 companies, including Chase Manhattan Bank, Prudential Insurance, and True North Media Services and has acted as a senior technical advisor for dozens of other projects.

Bob has been developing software for microcomputers since 1980, and has developed applications for many companies using a wide variety of development tools, languages, architectures, and operating systems. In 2004 he co-founded an information technology services company specializing in the Yellow Pages Advertising industry. As Chief Technology Officer he architected and implemented a service allowing agencies to view electronic "tear pages" via web services or interactively via a web site. The process included an optical character recognition (OCR) workflow automation process in for scanning, OCR, and review of Yellow Pages telephone directories. After 12 months of operation the database contained over 1.6 million scanned pages and accompanying extracted text in a searchable format.

He continues to be passionate about software development and technology in general. You can follow his thoughts on software development at http://blog.ramsoftsolutions.com

Comments and Discussions

 
QuestionSecurely storing pepper? Pin
Member 146474746-Nov-19 21:09
Member 146474746-Nov-19 21:09 
GeneralForgotten passwords Pin
Richard Deeming14-Oct-14 7:19
mveRichard Deeming14-Oct-14 7:19 
GeneralRe: Forgotten passwords Pin
Bob McGowan14-Oct-14 8:39
Bob McGowan14-Oct-14 8:39 
GeneralRe: Forgotten passwords Pin
Richard Deeming14-Oct-14 8:44
mveRichard Deeming14-Oct-14 8:44 
QuestionI was a little 'narked'... Pin
Garth J Lancaster23-Sep-14 15:02
professionalGarth J Lancaster23-Sep-14 15:02 
AnswerRe: I was a little 'narked'... Pin
Bob McGowan23-Sep-14 15:40
Bob McGowan23-Sep-14 15:40 
QuestionExplanation of the accessible salt value Pin
Peter Adam22-Sep-14 8:40
professionalPeter Adam22-Sep-14 8:40 
AnswerRe: Explanation of the accessible salt value Pin
Bob McGowan22-Sep-14 9:31
Bob McGowan22-Sep-14 9:31 
GeneralRe: Explanation of the accessible salt value Pin
Peter Adam22-Sep-14 10:07
professionalPeter Adam22-Sep-14 10:07 
GeneralMy vote of 4 Pin
George Jonsson21-Sep-14 21:54
professionalGeorge Jonsson21-Sep-14 21:54 
GeneralRe: My vote of 4 Pin
Bob McGowan22-Sep-14 2:46
Bob McGowan22-Sep-14 2:46 
GeneralRe: My vote of 4 Pin
George Jonsson22-Sep-14 15:07
professionalGeorge Jonsson22-Sep-14 15:07 
The only thing I can think of is that you could add some simple code examples. Most code monkeys (such as myself) like to see some code in an article. Smile | :)
Not strictly necessary, though.
Maybe you could elaborate a bit more on the salt and pepper sections.
GeneralRe: My vote of 4 Pin
Bob McGowan23-Sep-14 3:24
Bob McGowan23-Sep-14 3:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.