2
votes

I'm attempting to implement a stronger password hashing/storage mechanism and opting for the simplest approach, PHP's password_hash() and password_verify().

I'm letting PHP generate a random salt as recommended, and this all makes sense. Except, part of the "strong password" policy I need to implement is to reject a user from re-using a previously used password. In theory, I would be able to implement this by storing an archive of hashed passwords. When the user changes his/her password, we check the new hash against the archive of hashes. If there's a match, we know that the password has been previously used and reject it. However, implementing a random salt, while significantly more secure, effectively makes this archive of password hashes useless.

So, my question is how to implement this password policy while using random salts? Surely somebody else has run across this problem.

1
Why not store the random salt along with the generated password in the database so that you can check it against a match in the future ?Maximus2012
My thought is that if we store the random salt with each hashed password in the archive, then we'd have to re-hash the new password (when attempting to change) against every entry in the archive table to see if it matches. Alternatively, if we store the archived hashes with a static salt, wouldn't that be creating a weak point?Jeff Walden
Feck it, just store a plain text password :-)Kevin Nagurski
That is true but most likely you will need to do that only for a single user at a time and most likely they will not have a lot of entries in the database for password history. In either case, if you are using random salt for each user for each password then this might be the only way to implement it. You might also want to build a "pool" of random salts and try to re-use the values from the pool.Maximus2012

1 Answers

3
votes

To check against the previously used passwords, you do exactly the same as when checking against the current password, the random salt does not change anything. It is correct that you will have to verify each of the older hashes separately, to be sure that they are not equal, but this time intensive calculation must be done only when changing the password.

To check against a hash with salt, you need both the hash and its salt, they must have been stored together. The password_hash() function will include the salt in the resulting hash-value, so you do not have to care about storing the salt yourself.

$2y$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |                     |
 |  |  |                     hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |
 |  |  salt = nOUIs5kJ7naTuTFkBy1veu (22 characters)
 |  |
 |  cost-factor = 10 = 2^10 iterations
 |
 hash-algorithm = 2y = BCrypt

The password_verify() function will also support older hash formats, as long as they where produced with the crypt() function, this is because other parameters like the algorithm are stored as well. This makes the function future/past-proof.