Bitquark

Modern password hashing in PHP

Published

Historically, password security in PHP has been a bit slippery, requiring a measure of knowledge and care. Aiming to change that, PHP 5.5 introduces a special password_hash() function which makes password security much easier, and with features such as automatic algorithm upgrading, even more robust. There's also a compatibility library for PHP >= 5.3.7.

If you've ever looked at login code, the chances are you've seen developers using hash('sha256', $password), or even md5($password) to "secure" user passwords. Password hashes generated this way are laughably easy to crack; with weak algorithms and no salting or stretching in place you're almost giving your passwords to an attacker who gains access.

Salting? Stretching?

To salt a password you add a few random characters to it before hashing so that the same password will result in a unique string each time it is hashed, negating rainbow table attacks and making it necessary to crack each password individually. Salts are usually stored alongside the hash and must be used when checking passwords against the hash.

Stretching a password just involves hashing the resulting hash multiple times. This means that in order to check a password against a stolen hash, an attacker has to hash each guess multiple times, lengthening the time it takes to check each password hash. The effect is negligible for a single password check, but over thousands of iterations it soon adds up.

Enter password_hash()

The password_hash() function salts, stretches, and by default chooses the best hashing algorithm to use at the time of execution, meaning that you never have to worry about choosing an algorithm, or even updating your code to use to stronger algorithms as time moves on - if a better algorithm becomes available, the function will start using it for new hashes.

This last point is something I think will really help boost the security of PHP applications. It is made possible by a companion function, password_verify(), which is able to auto-detect the algorithm used when the password was hashed. Using this family of functions, it's trivial to run several different algorithms and password strength schemes in one place.

Here's an example of how to use the new fuction:

<?php
$hash = password_hash('ub3rs3cur3', PASSWORD_DEFAULT);
echo password_verify('ub3rs3cur3', $hash) ? 'Correct password!' : 'Incorrect password!';
?>

Hashes start with algorithm information, cost, and 22 alphanumeric salt characters, followed by the hashed password:

$2y$10$Ka3/TxAu3UrGX4E8suGkKO4V43dK9CcF.BTT5P8OzOO7/PRjqFn0a
$2y$10$6kf8iQDut0i7AOx2TULi1O9I5yxdSfX/T7HRy2KbMmliaYo4fLR.i
$2y$10$fPEY1vxgP15wwpfPdA22WOhkMqvmLsZtfzn9sr3rCw2V4N1tbEyle
$2y$10$878u5R1q8tP3karLHwBbAOfax8ybPt43U3F6lG9oOV5w9yfj/k1cq

That's all it takes to generate and verify a reasonably secure password in PHP. At the time of writing, Blowfish is the default best algorithm and a 60-character hash is generated, however as the PHP manual page notes, creating a password database field with a length of 255 characters may not be a bad idea to allow for future algorithmic expansion.

If you have decent hardware or aren't too fussed about it taking a couple of seconds to log in (I recommend considering this end of the scale), you can go one step further and increase the cost of the operation. The cost is the base-2 logarithm of the number of iterations to perform. The default cost is 10, the maximum for Blowfish is a cost of 31.

<?php
$hash = password_hash('ub3rs3cur3', PASSWORD_DEFAULT, array('cost' => 12));
?>

Password security

Of course, you still need to enforce a decent password policy to make sure that your users are choosing good passwords in the first place - strong password hashing won't do much for you if all of your passwords are set to Password1 or qwerty ;-)

For the first time PHP has tackled the problem of password security head on by implementing a set of functions specifically designed to securely hash and validate passwords, taking the guess work and myths out of the picture once and for all. I hope you consider making the switch, or perhaps using the new password mechanisms for new projects. The future is now!