PHP password_hash() / bcrypt

1.1k views Asked by At

I am checking out the bcrypt hash-algorithm.

My first test with password_hash():

echo password_hash("123", PASSWORD_BCRYPT, array( "salt" => "1234567890123456789012" ));
echo password_hash("123", PASSWORD_BCRYPT, array( "salt" => "1234567890123456789012xxxxxxxxxxxxxxx" ));

Both will return '$2y$10$123456789012345678901uiaLpJxTpf6VbfI5NADlsRsfvEm6aq9C'.

  1. Why the heck is the salt stored inside of the hash? This makes no sense at all for me. A attacker who gets the hashes from a database canĀ“t do anything with them, if he does not know the salt.
  2. Why do I get the same Hash with two different salts? Are only the first 22 chars used for the salt passed to the function?

Thank you very much!

2

There are 2 answers

4
adeneo On BEST ANSWER

The salt isn't a secret, it's generally stored in the database with the hash, and could just as well be stored directly in the hash, like password_hash does.

The salt creates uniqueness so the hash can't easily be cracked with things like rainbow tables or dictionaries, it doesn't really add security other than making the hash more unique so running a dictionary or table against the hash doesn't match because it also includes the salt.

If you omit the salt, a random salt will be generated by password_hash() for each password hashed. This is the intended mode of operation, and you shouldn't supply your own salts.
PHP7 will actually produce a warning telling you that using the salt option is deprecated.

The salt passed needs to be at least 22 characters, but most underlying algorithms, like bcrypt, doesn't use the entire salt, see this answer for more on that

0
ceejayoz On

Salts are not something you have to strive to keep secret. Their protection is effective even when known. https://crackstation.net/hashing-security.htm

The salt does not need to be secret. Just by randomizing the hashes, lookup tables, reverse lookup tables, and rainbow tables become ineffective. An attacker won't know in advance what the salt will be, so they can't pre-compute a lookup table or rainbow table. If each user's password is hashed with a different salt, the reverse lookup table attack won't work either.

Since you appear to be using a fixed salt value, heed this:

A common mistake is to use the same salt in each hash. Either the salt is hard-coded into the program, or is generated randomly once. This is ineffective because if two users have the same password, they'll still have the same hash. An attacker can still use a reverse lookup table attack to run a dictionary attack on every hash at the same time. They just have to apply the salt to each password guess before they hash it. If the salt is hard-coded into a popular product, lookup tables and rainbow tables can be built for that salt, to make it easier to crack hashes generated by the product.

I'd suggest using password_hash without its optional parameters. The default function is built to be highly secure and by specifying your own algorithm and options you potentially weaken its function.

Per the PHP documentation:

Caution It is strongly recommended that you do not generate your own salt for this function. It will create a secure salt automatically for you if you do not specify one.

edit: Here's why keeping the salt secret in bcrypt is pointless.

Per this post, there are 3,025,989,069,143,040 possible combinations in an 8 character password. You should generally tune bcrypt's work factor to take 0.1 seconds to hash a password. This means calculating all possibilities takes 302,598,906,914,304 seconds. That is 9,588 millennia.