34

Say I wanted to store a password for a user, would this be the right way to do it with PHP 5.5's password_hash() function (or this version for PHP 5.3.7+: https://github.com/ircmaxell/password_compat)?

$options = array("cost" => 10, "salt" => uniqid());
$hash = password_hash($password, PASSWORD_BCRYPT, $options);

Then I would do:

mysql_query("INSERT INTO users(username,password, salt) VALUES($username, $hash, " . $options['salt']);

To insert into database.

Then to verify:

$row = mysql_fetch_assoc(mysql_query("SELECT salt FROM users WHERE id=$userid"));
$salt = $row["salt"];
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10, "salt" => $salt));

if (password_verify($password, $hash) {
    // Verified
}
Machavity
  • 30,841
  • 27
  • 92
  • 100
Doug Smith
  • 29,668
  • 57
  • 204
  • 388
  • 1
    Hmm, there's something weird here, you have array("cost"=>10, and nothing after it? – Christian Stewart Feb 21 '13 at 00:12
  • Fixed, sorry about that. – Doug Smith Feb 21 '13 at 00:19
  • You're still missing a ) – Christian Stewart Feb 21 '13 at 00:19
  • Is it not working? Also you are using `ext/mysql`, which is deprecated in 5.5 I think – Explosion Pills Feb 21 '13 at 00:25
  • Oh, it'll work, I just want to make sure it's secure. – Doug Smith Feb 21 '13 at 00:28
  • 1
    I've never understood the use of a salt mechanism. If a hacker is attempting to brute force access user accounts, chances are they have access to your database or webserver, from which they can easily get the salt values for the accounts. Imo, using a salt mechanism is a false positive for security. – Mike Purcell Feb 21 '13 at 00:33
  • 4
    Then you should read up more, there's a lot more to salts than you appear to understand. – Doug Smith Feb 21 '13 at 00:37
  • There is a good writeup @ http://crackstation.net/hashing-security.htm, under the heading "Making Password Cracking Harder" there is this excerpt: "Salt ensures that attackers can't use specialized attacks like lookup tables and rainbow tables to crack large collections of hashes quickly, but it doesn't prevent them from running dictionary or brute-force attacks on each hash individually", which is the point I was trying to make. – Mike Purcell Feb 21 '13 at 00:49
  • 1
    No, but if you use a stretching algorithm (like `password_hash`) and increase computation time, you can make brute-force/dictionary attacks much less practical. – Pete Feb 21 '13 at 01:02

4 Answers4

59

Ignoring the issues with your database statements for now, I'll answer the question regarding password_hash.

In short, no, that is not how you do it. You do not want to store the salt alone, you should be storing both the hash and salt, and then using both to verify the password. password_hash returns a string containing both.

The password_hash function returns a string that contains both the hash and the salt. So:

$hashAndSalt = password_hash($password, PASSWORD_BCRYPT);
// Insert $hashAndSalt into database against user

Then to verify:

// Fetch hash+salt from database, place in $hashAndSalt variable
// and then to verify $password:
if (password_verify($password, $hashAndSalt)) {
   // Verified
}

Additionally, as the comments suggest, if you're interested in security you may want to look at mysqli (ext/mysql is deprecated in PHP5.5), and also this article on SQL injection: http://php.net/manual/en/security.database.sql-injection.php

ComFreek
  • 29,044
  • 18
  • 104
  • 156
Pete
  • 1,554
  • 11
  • 12
  • Are you suggesting that he use a salt mechanism, but not store the salt? If so, how will you retrieve the original salt used to create the original hash, upon which to check when user attempts to log in? – Mike Purcell Feb 21 '13 at 00:31
  • 1
    The salt and hash are returned in a single string together by password_hash. – Pete Feb 21 '13 at 00:32
  • When you say you'll ignore the issues with the database statements for now, were the issues the ones mentioned at the bottom of the post? – Doug Smith Feb 21 '13 at 00:34
  • @Doug - in essence, yes. You should be using `mysqli`, and embedding variables in strings like that leaves you open to SQL injection. – Pete Feb 21 '13 at 00:35
  • Ok so he returns salt from the `password_hash` function. Then the session is lost and unable to login via cookie credentials. The user attempts to log in again, how are you going to get the original salt in order to create the proper hash to check against the hash stored in the database? – Mike Purcell Feb 21 '13 at 00:36
  • It's stored **with** the hash. `password_hash` returns them both in one string. You pull the salt/hash string from the database, and run it through `password_verify`. `password_verify` extracts the salt and hash from the hash/salt string, and performs the comparison for you. – Pete Feb 21 '13 at 00:39
  • Can you post an example of what an row in the table may look like? – Mike Purcell Feb 21 '13 at 00:41
  • 4
    I think you might find reading this: https://wiki.php.net/rfc/password_hash and this: http://www.php.net/password-hash more productive. – Pete Feb 21 '13 at 00:46
  • I understand how it works, I was curious how you were suggesting it be implemented. In your first iteration it read as though you were suggesting that the OP does not store the salt, either I misread it, or you corrected it via edits. Either way your current version appears correct, if he opts to use salt. – Mike Purcell Feb 21 '13 at 00:51
  • As mentioned above, I did edit it to make it clearer - I was never intentionally suggesting he didn't store the salt. As you noted, that would make it rather pointless. – Pete Feb 21 '13 at 00:53
  • But with password_hash, does it use a unique salt if you just use those two parameters? Or do you have to supply another? (If you don't, what's the point of the options array?) – Doug Smith Feb 21 '13 at 00:54
  • 2
    From php.net/password-hash: "If omitted, a random salt will be created and the default cost will be used." I assume that the options are there for those that like a little more control. The defaults should be fine, though. – Pete Feb 21 '13 at 00:54
  • Really appreciate the help. – Doug Smith Feb 21 '13 at 00:58
  • 7
    @DougSmith - You should **not** create your own salt, `password_hash()` will create a cryptographically safe salt from the random source of the operating system. It has fallbacks implemented to make the salt as good as possible. – martinstoeckli Feb 21 '13 at 10:47
  • Thanks mate, helped a bunch. Is it recommended to use your own salt and hash? Or should i just make passwords like: `password_hash($password,PASSWORD_DEFAULT);` ? – Refilon Jan 06 '15 at 14:24
11

Using your own salt is not recommended and, as of PHP 7, its use is deprecated. To understand why, the author of password_hash shared these thoughts (link defunct)

One thing has become abundantly clear to me: the salt option is dangerous. I've yet to see a single usage of the salt option that has been even decent. Every usage ranges from bad (passing mt_rand() output) to dangerous (static strings) to insane (passing the password as its own salt).

I've come to the conclusion that I don't think we should allow users to specify the salt.

He even made this comment in SO chat noting how bad passing your own salt can be

Machavity
  • 30,841
  • 27
  • 92
  • 100
7

Note this from php.net

Warning

The salt option has been deprecated as of PHP 7.0.0. It is now preferred to simply use the salt that is generated by default.

Conclusion? Forget about salt option.

This would be quite enough password_hash('password', PASSWORD_DEFAULT) *(or _BCRYPT)

Spooky
  • 1,235
  • 15
  • 17
5

You should not enter own salt, leave salt empty, function will generate good random salt.

Insert into database (or file or whatever you use) whole the string returned by the function. it contains: id of algorithm, cost, salt (22 chars) and hash password.

The entire string is required to use password_verify (). Salt is random and does not harm to fall into the wrong hands (with hashed password). This prevents (or very difficult) to use ready sets generated lists of passwords and hashes - rainbow tables.

You should consider add cost parameter. Default (if omitted) is 10 - if higher then function compute hash longer. Increasing the cost by 1, double time needed to generate a hash (and thus lengthen the time it takes to break password)

$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10));

you should set this parameter based on speed check on your server. It is recommended that the function performed 100ms+ (some prefer to make it 250 ms). Usually cost = 10 or 11 is a good choice (in 2015).

To increase security, you might want to add to passwords a long (50-60 characters is good choice) secret string. before you use password_hash() or password_verify().

$secret_string = 'asCaahC72D2bywdu@#$@#$234';
$password  = trim($_POST['user_password']) . $secret_string;
// here use password_* function

Caution Using the PASSWORD_BCRYPT for the algo parameter, will result in the password parameter being truncated to a maximum length of 72 characters.

If $password will be longer than 72 chars and you change or add 73 or 90 characters hash will not change. Optional, sticking $secret_string should be at the end (after the user's password and not before).

Adam
  • 314
  • 2
  • 9