[ACCEPTED]-How can I store my users' passwords safely?-password-hash

Accepted answer
Score: 275

The easiest way to get your password storage 39 scheme secure is by using a standard library.

Because security tends 38 to be a lot more complicated and with more 37 invisible screw up possibilities than most 36 programmers could tackle alone, using a 35 standard library is almost always easiest 34 and most secure (if not the only) available 33 option.

The new PHP password API (5.5.0+)

If you are using PHP version 5.5.0 32 or newer, you can use the new simplified 31 password hashing API

Example of code using 30 PHP's password API:

// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';

(In case you are still 29 using legacy 5.3.7 or newer you can install 28 ircmaxell/password_compat to have access to the build-in functions)

Improving upon salted hashes: add pepper

If 27 you want extra security, the security folks 26 now (2017) recommend adding a 'pepper' to the 25 (automatically) salted password hashes.

There 24 is a simple, drop in class that securely 23 implements this pattern, I recommend: Netsilik/PepperedPasswords (github).
It 22 comes with a MIT License, so you can use 21 it however you want, even in proprietary 20 projects.

Example of code using Netsilik/PepperedPasswords:

use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';

The OLD standard library

Please note: you should 19 not be needing this anymore! This is only 18 here for historical purposes.

Take a look 17 at: Portable PHP password hashing framework: phpass and make sure you use the CRYPT_BLOWFISH algorithm 16 if at all possible.

Example of code using 15 phpass (v0.2):


$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';

PHPass has been implemented 14 in some quite well known projects:

  • phpBB3
  • WordPress 2.5+ as well as bbPress
  • the Drupal 7 release, (module available for Drupal 5 & 6)
  • others

The good 13 thing is that you do not need to worry about 12 the details, those details have been programmed 11 by people with experience and reviewed by 10 many folks on the internet.

For more information 9 on password storage schemes, read Jeff`s blog 8 post: You're Probably Storing Passwords Incorrectly

Whatever you do if you go for the 7 'I'll do it myself, thank you' approach, do not use MD5 or SHA1 anymore. They are nice hashing algorithm, but 6 considered broken for security purposes.

Currently, using crypt, with CRYPT_BLOWFISH 5 is the best practice.
CRYPT_BLOWFISH in 4 PHP is an implementation of the Bcrypt hash. Bcrypt 3 is based on the Blowfish block cipher, making 2 use of it's expensive key setup to slow 1 the algorithm down.

Score: 31

Your users will be much safer if you used 4 parameterized queries instead of concatenating 3 SQL statements. And the salt should be unique for 2 each user and should be stored along with 1 the password hash.

Score: 12

A better way would be for each user to have 16 a unique salt.

The benefit of having a salt 15 is that it makes it harder for an attacker 14 to pre-generate the MD5 signature of every 13 dictionary word. But if an attacker learns 12 that you have a fixed salt, they could then 11 pre-generate the MD5 signature of every 10 dictionary word prefixed by your fixed salt.

A 9 better way is each time a user changes their 8 password, your system generate a random 7 salt and store that salt along with the 6 user record. It makes it a bit more expensive 5 to check the password (since you need to 4 look up the salt before you can generate 3 the MD5 signature) but it makes it much 2 more difficult for an attacker to pre-generate 1 MD5's.

Score: 12

With PHP 5.5 (what I describe is available 26 to even earlier versions, see below) around 25 the corner I'd like to suggest to use its 24 new, built-in solution: password_hash() and password_verify(). It provides 23 several options in order to achieve the 22 level of password security you need (for 21 example by specifying a "cost" parameter 20 through the $options array)

var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));

will return

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

As you might 19 see, the string contains the salt as well 18 as the cost that was specified in the options. It 17 also contains the algorithm used.

Therefore, when 16 checking the password (for example when 15 the user logs in), when using the complimentary 14 password_verify() function it will extract the necessary 13 crypto parameters from the password hash 12 itself.

When not specifying a salt, the generated 11 password hash will be different upon every 10 call of password_hash() because the salt is generated randomly. Therefore 9 comparing a previous hash with a newly generated 8 one will fail, even for a correct password.

Verifying 7 works like this:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

I hope that providing these 6 built-in functions will soon provide better 5 password security in case of data theft, as 4 it reduces the amount of thought the programmer 3 has to put into a proper implementation.

There 2 is a small library (one PHP file) that will 1 give you PHP 5.5's password_hash in PHP 5.3.7+: https://github.com/ircmaxell/password_compat

Score: 0

That's fine with me. Mr Atwood wrote about 5 the strength of MD5 against rainbow tables, and basically with a long salt like that 4 you're sitting pretty (though some random 3 punctuation/numbers, it could improve it).

You 2 could also look at SHA-1, which seems to 1 be getting more popular these days.

Score: 0

I want to add:

  • Don't limit users passwords by length

For compatibility with old 10 systems often set a limit for the maximum 9 length of the password. This is a bad security 8 policy: if you set restriction, set it only 7 for the minimum length of passwords.

  • Don't send user passwords via email

For 6 recovering a forgotten password you should 5 send the address by which user can change 4 the password.

  • Update the hashes of users passwords

The password hash may be out 3 of date (parameters of the algorithm may 2 be updated). By using the function password_needs_rehash() you 1 can check it out.

More Related questions