If you're storing passwords for user accounts, you should use bcrypt. Don't argue. Just do it.
Do it more easily by using this library:
require('lib_bcrypt.php');
$hasher = new BCryptHasher();
$hash = $hasher->HashPassword($plaintext_password);
Store $hash
in your database. When a user logs in and you
need to check their password is correct:
if ($hasher->CheckPassword($entered_password, $hash)){
# password is legit
}else{
# Y U NO USE CORRECT PASSWORD????
}
There is only one option that you can use while creating a password hash - the work factor. You can pass a value between 4 and 31:
$weak_hash = $hasher->HashPassword($plaintext_password, 4);
$crazy_hash = $hasher->HashPassword($plaintext_password, 31);
This value is the base-2 logarithm of the iteration count of the hashing function. This means that the bigger the number, the slower the hashing. Slow is a good thing!
Some example timings for computing a single hash on my laptop:
Work Factor | Approx. Time |
---|---|
4 | 2 ms |
6 | 6 ms |
8 | 25 ms |
10 | 105 ms |
12 | 400 ms |
14 | 1700 ms |
The default value is 8, which is pretty fast (though much slower than
md5()
or sha1()
). You might want to pick a higher value if you have
beefy servers. The bigger the better, but balance it against how often
your app needs to validate logins. Allowing your servers to be DOS'd by
submitting a few hundred login attempts per second would suck.
Because the work factor is built into the hash, you can change the value you use over time in your application and the code that checks for valid passwords will not need to be changed.
If you already store your passwords using md5()
, sha1()
or something
similar, you can't easily generate bcrypt hashes, since you don't have
the plaintext passwords.
But it's ok, you can fix this.
All bcrypt hashes start with the string $2a$
, so it's easy to tell if
a stored hash is from bcrypt or not. When your user logs in, have code
something like this:
if (substr($hash, 0, 4) == '$2a$'){
# good, we have a bcrypt hash already
$is_ok = $hasher->CheckPassword($entered_password, $hash);
}else{
# old, bad, hash
$is_ok = md5($entered_password) == $hash;
if ($is_ok) update_stored_hash($hasher->HashPassword($entered_password));
}
Using a mechanism like this, you can upgrade your stored hashes the next time a user logs in (or changes their password, or whatever).
If you want to get fancy, you could detect the work factor used to generate the hash you've already got stored and re-hash the password if it's too low. Using this technique, you can keep increasing your work factor as servers become more powerful.
If you have perl's Test::Harness installed (you almost certainly do), you can run the tests using:
prove --exec 'php' test.php
Bcrypt will only use the first 72 bytes of the password as part of the hash. If you supply a longer string, only the first 72 bytes will be hashed.