How to safely store a password

Most developers will have to handle authentication at some point, and this usually means storing a password. Everyone knows that storing plain-text passwords is a Bad Idea, but this raises the question; how can I store the password safely?

If you store passwords as plain-text, stop it! Right now. There is no good reason to store passwords as plain text, and it should never be done. Database security will be breached, no matter how secure you make your system. When that happens, an attacker has the login details for every user in your system. Knowing that people often use the same passwords for multiple systems, that plain-text password could open up your users to attacks on their social networks, email, or even bank accounts. This will land you as the system administrator in legal trouble, as most countries have very strict laws governing the storage of personal data.

Password storage basic principles

The first step to safely storing a password is to not store the password at all. You should be storing a cryptographic hash of the password. A cryptographic hash is a one-way function; it is easy to create one for a password, but almost impossible to get the original password out.

To authenticate against a stored hash, you take the plain-text password provided by your user, concatenate it with the salt for that user, generate a hash using the same algorithm as your stored hash, and if the newly generated hash matches the stored version, the password is valid. The concept of salt is discussed later in this article.

There are password-less authentication schemes like OAuth that don’t require you to store or transmit the password or a hash of it. But that is beyond the scope of this article.

Features of a safely stored password

1 Password is hashed using a secure one-way hash

There are many cryptographic hash functions, and the next step is deciding which one to use. Historically MD5 was the algorithm of choice, but some clever maths geezers figured out how to reverse MD5 very quickly and it should not be used now. SHA–1 is another popular choice, though it too is close to being broken in the same way as MD5.

Next we have the SHA2 family of algorithms, SHA–256, and SHA–512 being the most popular variants. These are fine choices and are widely supported. They are not close to being broken, and provide an adequate level of security against most categories of attack.

Alternatively, the Bcrypt (BlowfishCrypt) algorithm is at least as secure as the SHA2 family, but has one advantage - it is resistant to brute force attacks as it includes an extra “cost” that determines how computationally expensive it is to calculate the hash. It gets around the brute force problem by being purposefully slow to compute.

There are other algorithms, and the SHA3 competition is underway. However these are the most commonly used, and are what this article focuses on.

2 Hashing algorithm should be resistant to brute-force attack

All these hash algorithms have one flaw in common. They are designed to be quick, and are thus vulnerable to brute forcing.

On standard desktop hardware with off-the-shelf software you can generate 2,300,000,000 SHA–1 hashes every second. That’s not a typo, 2.3 billion hashes a second. Even using pure perl tools and using non-optimized code I can generate 1.3 million hashes a second on my year-old laptop.

2.3 billion hashes a second means every single possible combination of alphanumeric passwords less than 8 characters can be brute forced in 23 minutes. If your password is less than 8 characters and is stored as a salted SHA–1 hash (A common scenario) - it is effectively plain text.

Bcrypt can mitigate this class of attack by setting the cost to a high value. A higher cost forces the algorithm to generate hashes at a slower rate, making brute force attacks take an unfeasibly long amount of time again.

3 Password hash has a unique, cryptographically random salt

In addition to storing the hash of the password rather than the plain-text, you must add a salt to it. The purpose of a salt is to make sure that the same password does not always generate the same hash. A salt is a bunch of random characters that are concatenated with the password before hashing. You must use a different salt for every password hash you generate for it to be effective.

There is widespread misunderstanding about what a salt is supposed to do. Some people treat a salt like a secret key, specific to their application and use the same salt with every password - meaning the original point of a salt, to stop the same passwords hashing to the same value - is lost.

Salt isn’t any more secret than the password hash itself and should be stored along with the password hash. It isn’t a secret key, and doesn’t help preventing brute force attacks. If someone has access to your database and the password hashes, they have access to your salt too. Don’t treat salt like a secret key.

An ideal salt should be cryptographically random, so the chance that two hashes having the same salt is minimal.

Recommended Best Practice?

The bcrypt algorithm is recommended for all new development work. Its resistance to brute force attacks is a key factor, and is only going to increase it’s appeal as computers get ever faster and start generating trillions of hashes per second.

If your legacy code is using SHA–256 or SHA–512, AND uses a random salt for every password - you should be fine for the time being. Brute forcing a SHA–512 hash still takes a length of time that borders of practicality.

If you use SHA–1, MD5, or store passwords as plain-text - you need to change your policy right away. Your passwords are insecure, and at serious risk when your database is breached.

If you don’t have a random salt for each user, you should start doing so right away. Modules like Data::Entropy provide easy ways to get good random-ness.

Common Questions

1 No one will breach my security and access my database

That is a bad attitude to have. Recent news shows database compromises on the most secure systems, staffed by experts with lots of money.

Do you really believe that your system is inherently more secure than theirs?

2 Why not my own custom, secret algorithm?

Just because you can’t find a shortcut to your algorithm, it doesn’t mean that there isn’t one.

The hashing algorithms above have had some of the greatest mathematical minds of the 20th century working on them for the past 40 years, and they still made mistakes - leaving MD5 and SHA–1 vulnerable.

Hubris is a characteristic of a great programmer, but it is highly unlikely that you are better at crypto-algorithms than the people who created these general purpose hashing algorithms

3 What modules should I use to hash my passwords?

As with everything in perl, There Is More Than One Way To Do It.

If you want to do everything manually, you can use the venerable Digest set of modules, or Crypt::Eksblowfish::Bcrypt if you are using bcrypt.

If you want a nicer interface that manages the dangerous bits for you, the Authen::Passphrase set of modules is a good choice.

If you are using Dancer, you can use my own Dancer::Plugin::Passphrase module. It has an interface similar to Authen::Passphrase, and manages all the common hashing algorithms

Dancer::Plugin::Passphrase

The plugin exports the passphrase keyword, to which you pass a plaintext password. From this you can generate a hash using whatever algorithm you specify. It defaults to bcrypt with a work factor of 4.

You can match a plaintext password to a stored hash by using the matches method.

An example Dancer app is below. See the documentation of Dancer::Plugin::Passphrase for further details

package MyWebService;
use Dancer ':syntax';
use Dancer::Plugin::Passphrase;
 
post '/save_password' sub => {
    my $hash = passphrase( param('password') )->generate_hash;
 
    # [...] Store $hash in DB
};
 
get '/check_password' sub => {
    # [...] Retrieve $stored_hash from the DB
 
    if ( passphrase( param('password') )->matches( $stored_hash ) ) {
        # Password matches!
    }
};

TL;DR?

Use Bcrypt.

References and Further Reading

Digest Data::Entropy Authen::Passphrase Crypt::Eksblowfish::Bcrypt Dancer::Plugin::Passphrase

AUTHOR

This article was written by James Aitken. LoonyPandora on Twitter/GitHub, JAITKEN on CPAN

blog comments powered by Disqus