Password Storage Best Practices


Share This Article


Password Protection ImageWith several high profile security breaches so far this year, security is on the forefront of most developers minds, or it should be. Though, if one has never coded good authentication code before, it can be a rather daunting task. The Internet is rife with poor advice, old advice, or just plain wrong advice. This post will attempt to demystify some of the tools and techniques of building authentication systems.

Most authentication services have different requirements. For example, you would expect there to be more security logging into your bank than into your blogging software. At a point, you have to establish what authentication is good enough for the data you're protecting. Users are more likely to not use your system for every additional hoop they have to jump thorough, but if they view the data as worth it, they might be willing to be further inconvenienced (For example, two-factor authentication with a key fob for logging into a bank)

Never Store Passwords as Plain Text.

This is the starting point for all following advice. Passwords should never exist in plain text longer than the time it takes to hash them. They should never be persisted to disk in any way (database, cookie, text file, etc.). Also, plain text passwords should never travel across the wire, even over SSL, if it can be helped. As this rule can not be applied to all situations (web based log-ins, for example), this is a good-to-have if designing a system that stores more sensitive data. However, SSL should be viewed as a minimum requirement for transmitting passwords over the web.

Always Use a One-Way Hash to Store The Password.

Some password recovery systems email you your password if you click the Password Reminder link on the login form. This is a sure sign that they are either storing their passwords as plain text, or using encryption to store the password. If an attacker convinces your system to send them the encryption key for the passwords (File system access, poor permissions to the file holding the key), the passwords are then essentially stored in plain text for that attacker. Passwords should be hashed via a one-way function. This ensures that even if the attacker does obtain a copy of your password database, there are still hurdles in their way before they can recover the passwords for the user accounts. However, this leads into the next few points.

Never Roll Your Own Hash Function.

All of the major hashes and cryptographic systems in use are widely publicized with whole communities of people working to uncover flaws in the algorithms. NIST has standardized on several hash functions which have been mathematically analyzed and have no apparent flaws. You and your co-workers are the only ones that can see the hash function you wrote. Do you know how likely it is that two strings will produce the same result? Or how quickly it could be broken?

MD5 and SHA-1 Are Considered Broken. Use SHA-2 At Minimum.

Practical collision attacks against MD5 have been demonstrated, with software able to produce collisions within minutes on modern hardware. SHA1 is getting within the realm of cracking with off-the-shelf hardware (GPUs have been a major contributor to faster cracking. GPUs with hundreds of compute units available for under $200 and multi-core commodity hardware could not have been predicted with MD5 and SHA-1 were created). With MD5 having a 128-bit space, and SHA1 having a 160-bit space, the shorter key spaces and faster hardware are a major factor in these hashes being considered broken. The US Government has stopped using MD5 and SHA1 for cryptographic purposes.

SHA-2 comes in several variants, with key spaces of 224, 256, 384 and 512 bits. With these enlarged key spaces, there should be several more years of safety with these key lengths. However, the SHA-2 family is based off of the SHA-1 function. If an attack is discovered against SHA-1, it will be applicable to the SHA-2 family. NIST is currently holding a competition to choose a hash function that will become SHA-3, which should be chosen sometime in 2012.

Salt Your Passwords.

So, an attacker has your password database, are you vulnerable? If you’re just hashing your passwords, yes. As mentioned above, GPUs and multi-core desktops have made most hash algorithms vulnerable to brute-force attacks. Brute-force programs like John The Ripper have been modified to run on GPUs with a massive performance boost. In addition to brute-forcing, most users do not choose good passwords (Dictionary words, all numbers, names, etc.). For these, the attackers have another tool at their disposal: Rainbow Tables. These are large lists that contain strings (say for two word dictionary word combinations) and their hashes. Instead of guessing a password, hashing it, and then comparing to the password from your password database, they scan the rainbow table for the hashes from your database, and if there is a match, they have the password.

There are two techniques you can use to slow down an attacker: Salting and hashing multiple times. Salting refers to generating a chunk of random data, and concatenating it to the users’ password before hashing. Salts can be stored along side the password hash in the database, as they themselves are not considered secure. Salts should be unique per-user, so users with the same password will not have the same hash. Generating salts should be simple, as shown in the following snippet:

private readonly RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

 

public string GenerateSalt()
{
var saltBytes = new byte[32];
rng.GetBytes(saltBytes);
return Convert.ToBase64String(saltBytes);
}

 

This generate a 32 byte (256 bit) salt, and returns it in Base64 for easy database storage. Note the use of the RNGCryptoServiceProvider instead of the general Random class. This ensures high quality random numbers. Then when generating the password hash, you simply:

 

SHA256Managed sha256 = new SHA256Managed();
string saltedPassword = salt + clearPassword;
byte[] thisHash = sha256.ComputeHash(Encoding.UTF8.GetBytes(saltedPassword));

 

With “salt” being the salt generated by the GenerateSalt function, “clearPassword” is the password the user entered. With this, your password database is resistant to rainbow table attacks for the foreseeable future.

And Hash Them Multiple Times.

Now, moving onto the next attack: Brute-force. If the attacker has your hashes, and your salts, they can still easily guess users with weak passwords, right? This is where the multiple hashings come in. Computing each hash takes time, this is why rainbow tables win with speed. With brute-forcing, the attacker has to compute the hash of every salt+password combination. With GPUs and distributed computing, it’s becoming easier to run several hashes in parallel for a speed boost.

With hashing multiple times, you make the attacker perform more work before the password can be verified. For example, on my desktop, computing the SHA-256 hash of a 21 character string (far longer than most passwords) takes about 2.5 microseconds. This means that a single thread on my desktop can check, roughly, 400,000 passwords a second. On a quad-core box, this becomes 12,000,000 passwords per second. That means I can check ever 8 character all-lower-case passwords in a little under 5 hours. Now, what happens if we make an attacker run the hash 2^10 times (2048)? That means 1 check requires 5 milliseconds on my desktop. This leads to 200 passwords per second on a single thread, or 800 per second using 4 cores. This results in a little over 8 years checking all 8 character lower-case passwords.

While this does slow down processing on the server, if you’re validating more than a few hundred users per second, you’d be using a server farm with 8-12 core CPUs.

A simple code snippet for doing this kind of password hashing follows:

 

public string HashPassword(string clearPassword, string salt)
{
var roundCount = 2<<LogRounds;
var saltedPassword = salt + clearPassword;
var saltedPasswordBytes = Encoding.UTF8.GetBytes(saltedPassword);
var thisHash = sha256.ComputeHash(saltedPasswordBytes);
for (var i = 0; i < roundCount;i++ )
{
var nextBytes = thisHash.Concat(saltedPasswordBytes).ToArray();
thisHash = sha256.ComputeHash(nextBytes);
}
return Convert.ToBase64String(thisHash);
}

 

LogRounds is a constant defined within the program, for my example above, it would be 10. Then it simply does one initial hash of the password, and then hashes in a loop, taking the last hash, and the password and salt, and then hashing that again.

Conclusion

If these steps are followed when dealing with user passwords, your system will be much more resilient to attacks, even if the attacker has access to your code base and password store.