How to Work With Users' Passwords and How to Securely Hash Passwords in PHP?

When you must save user’s password in a database you should never ever store them in plain text because of security precautions and privacy protection. Database where users’ passwords are stored might get compromised and by hashing them at least there is another safety mechanism for not revealing them to the attacker.

<?php

// plain text password example
$password = 'secretcode';

Cryptography is a large and quite complex field for a lot of people so a good rule of a thumb would be to leave it to the experts.

One of the most used but wrong way of hashing password was once using md5() which calculates md5 hash of a string. Hashing passwords with md5 (or sha1 or even sha256) is note safe anymore because these hashes can get decrypted very fast.

<?php

// plain text password
$password = 'secretcode';

// hash the password with md5
$md5 = md5($password);

Common solution to preventing decryption is using the salt.

<?php

// plain text password
$password = 'secretcode';

// add random number of random characters - the salt
$salt = '3x%%$bf83#dls2qgdf';

// hash salt and password together
$md5 = md5($salt.$password);

This is still not good enough though - Rainbow tables.

Right Way of Hashing Passwords in PHP

Right way of hashing passwords is currently using latest PHP version and its native passwords hashing API which provides an easy to use wrapper around crypt function.

Example of PHP native password hashing API usage:

<?php

// plain text password
$password = 'secretcode';

$options = ['cost' => 12];
echo password_hash($password, PASSWORD_DEFAULT, $options);

In password_hash() function there are currently two types of algorithm options available. PASSWORD_DEFAULT and PASSWORD_BCRYPT. Currently PASSWORD_DEFAULT is PASSWORD_BCRYPT and as language and cryptography progress there will be different types of algorithms supported. PASSWORD_DEFAULT will get replaced with that new type of algorithm (for example, Argon2). Good choice is to always use the PASSWORD_DEFAULT.

The database password’s field type should be varchar(255) for future proof algorithm changes.

Using your own salt is not a very good option. Leave that to the experts as well and use above bullet proof solution without setting your own salt. Salt is randomly generated by default in password_hash() function.

Another option that is important to mention is the cost which controls the hash speed. On servers with better resources cost can be increased. There is a script for calculating the cost for your environment in the PHP manual. As a good security practice try increasing this to higher value than the default 10.

Verifying passwords can be done with password_verify():

<?php

// this is the hash of the password in above example
$hash = '$2y$12$VD3vCfuHcxU0zcgDvArQSOlQmPv3tXW0TWoteV4QvBYL66khev0oq';

if (password_verify('secretcode', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}

Another useful function is password_needs_rehash() - which checks if given hash matches given options. This comes handy in case of server hardware upgrade and therefore increasing the cost option.

The hash string returned by password_hash() consists of the following parts:

$algorithm = substr($hash, 0, 4);  // $2y$ == BLOWFISH
$cost      = substr($hash, 4, 2);  // the cost
$salt      = substr($hash, 7, 22); // salt is automatically generated by default

Password Hashing in Older PHP Versions (5.5 and Below)

In case you’re still using some older PHP version, there is a way to secure passwords securely. Since PHP version > 5.3.7 you can use PHP library password_compat. PHP library password_compat works exactly the same way as does the native PHP password hashing API, so when you upgrade to latest PHP you will not need to refactor your code.

For PHP version below 5.3.6 phpass might be a good solution, but try to avoid these and use the native password hashing API.

Password Hashing in Open Source Projects

Some of the widely used PHP open source projects use different hashing algorithms for passwords because they either support older PHP versions where password_hash() wasn’t available yet or they already use the latest security recommendations by PHP security experts:

Project Password hashing
CMS Airship Argon2i
Drupal SHA512Crypt with multiple rounds
Joomla bcrypt
Laravel bcrypt with other options
Symfony bcrypt with other options
Wordpress salted MD5

See Also

GitHub OctocatFound a typo? Something wrong with this content? Just fork and edit it.

Content of this work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license. Code snippets in examples are published under the CC0 1.0 Universal (CC0 1.0). Thanks to all the contributors.