This answer was written a year ago, and as I've learned more in the past year I figured that I should bring this up to date. Understand that this answer will be quite different than it was before.
It is completely impossible to store a password in complete security. Accept that. If your database is stolen then your users are at risk; it's only a matter of time and money before their password is stolen. Therefore, as programmers our job is to make it as hard as possible to recover this password.
Yes, there are crypt
and bcrypt
but I do not trust them. Blowfish is a cipher, and to get repeatable results from a cipher you must have a password--if there is a password it can be reversed. Blowfish was never designed to be destructive to data. It is simply slow and expensive to recover these passwords, but I'm certain that there is a way to decrypt them to an easy to crack condition.
Since I distrust crypt
I use the following tricks to make it more expensive to crack stored passwords.
Key Principles
Use HMAC, not a straight hash. HMACs are slower and require a key that must also be compromised, and their state cannot be saved for minor alterations to the data (IE, they must be 100% processed every time, no shortcuts).
Generate a nonce for each user; this alone defeats the rainbow table. This is a random number that, depending on the range, expands how many resulting hashes there are.
Generate a unique "site key" that is a 60 - 80 character code, used as the HMAC's key.
Bad Practices
Emailing a password to your users.
Keeping logs of the passwords entered into the login form.
Storing passwords somewhere else in unencrypted form.
Assuming that your site/software is too unknown to be affected.
Sample Code
In PHP the function hash_hmac
is called with three arguments: the hash algorithm, the data, and the key. For this example I've used a global variable that acts as a site key, presumably a very large string unique to the installation that was stored in a configuration file.
function hash_password($password, $nonce) {
global $site_key;
return hash_hmac('sha512', $password . $nonce, $site_key);
}
This code requires PHP 5.1.2 or newer. Older versions can include the PECL Hash package.
Comparison Against the Old Answer
Effectively, the nonce and site key both act as salts. The site key is static, yes, but it is used as additional entropy in the hashing process. The nonce, being random, means that unless two users have the exact same nonce a full rainbow table must be generated for each. SHA-512 is a stronger hash than SHA1, and will naturally take longer to process; the database storage of the password will need to be 128 characters long.
However, my old answer has some potential problems that this new solution does not. A hash, without HMAC, follows a process that can be continued from any point: for example, a salt of "boo" has a specific has that can be continued to the full password's length without having to recalculate the salt value's hash. There is no truly effective way to work around this flaw of basic hashing. This necessitates calculating an HMAC instead of a simple hash.
Do not store the site key in your database, as the most common compromise is access to the database. With the site key in the file system it must be guessed, or the file system attacked. And always protect your usernames and passwords to both the database and file system vigorously!
As I Said Last Time...
Let users make their password as long as they like, but enforce a minimum length of 9 characters. Your hash won't grow in size if they enter a huge password, and it's no extra work to deal with. A minimum length of 9 ensures that there are 26^9
combinations for all lower case input, 52^9
for mixed case, and so on. Users hate to be forced to remember long and convoluted passwords, especially if they're elderly. But at least a 9 character long password will take a bit of time to force.
While the computational power to crack a hash doesn't exist, and this hash [my old method] is roughly as secure as they come ... none of it will guard against a weak password. For this purpose brute-force attacks will always be more effective, and so must be guarded against more strongly.
And last but not least: I am not an expert. Be as paranoid as possible, make things as hard to intrude as possible, then, if you are still worried, contact a white-hat hacker to see what they say about your code/system.