-->

Why is php's password_hash so slow?

2020-06-12 06:42发布

问题:

I am using password_hash for password encryption. However there is a strange question, password_hash cost very long time. Here is a sample code. this code will cost more than 1 second. Is that normal?

<?php
  $startTime = microtime(TRUE);
  $password='123456';
  $cost=13;
  $hash=password_hash($password, PASSWORD_DEFAULT, ['cost' => $cost]);
  password_verify($password,$hash);
  $endTime = microtime(TRUE);
  $time = $endTime - $startTime;
  echo $time;
?>

the result is :1.0858609676361

回答1:

After running on 3v4l that seems perfectly normal.

Password hashing is not something you want optimize. In the words of Leigh on the hash documentation:

If you are hashing passwords etc for security, speed is not your friend. You should use the slowest method.

Slow to hash means slow to crack and will hopefully make generating things like rainbow tables more trouble than it's worth.



回答2:

The default algorithm for password_hash, bcrypt, is designed to be slow.

http://en.wikipedia.org/wiki/Key_stretching

In cryptography, key stretching refers to techniques used to make a possibly weak key, typically a password or passphrase, more secure against a brute force attack by increasing the time it takes to test each possible key. Passwords or passphrases created by humans are often short or predictable enough to allow password cracking. Key stretching makes such attacks more difficult.

http://en.wikipedia.org/wiki/Rainbow_table#Defense_against_rainbow_tables

Another technique that helps prevent precomputation attacks is key stretching. When stretching is used, the salt, password, and a number of intermediate hash values are run through the underlying hash function multiple times to increase the computation time required to hash each password. For instance, MD5-Crypt uses a 1000 iteration loop that repeatedly feeds the salt, password, and current intermediate hash value back into the underlying MD5 hash function. The user's password hash is the concatenation of the salt value (which is not secret) and the final hash. The extra time is not noticeable to users because they have to wait only a fraction of a second each time they log in. On the other hand, stretching reduces the effectiveness of a brute-force attacks in proportion to the number of iterations because it reduces the number of computations an attacker can perform in a given time frame. This principle is applied in MD5-Crypt and in bcrypt. It also greatly increases the time needed to build a precomputed table, but in the absence of salt, this needs only be done once.

A full second is probably a little long - you could experiment with dropping $cost by one or two to bring it more to something like a tenth of a second, which will retain the effective protection while making the delay unnoticeable to your users.



回答3:

Yes, it's normal. That's what the cost parameter is for: it allows you to tweak the iteration count, making the hash slower or faster as needed.

You should always make the hash as slow as possible and as fast as necessary. The reason being that the only feasible attack on a password hash is brute force. You want to make the cost so large that it takes prohibitively long to simple brute force all possible values. That's your only real defence against attackers with password hashing to begin with.

One whole second seems prohibitively for your own use. You should lower that cost a bit to stay within a few hundred milliseconds at most. Adjust to your target systems as needed.



回答4:

To begin, password_hash is not encryption.

password_hash() creates a new password hash using a strong one-way hashing algorithm. password_hash() is compatible with crypt(). Therefore, password hashes created by crypt() can be used with password_hash().

A hash is one-way, and whatever you pass into it will always have the same end-result, however there is no way for you get the original string from the hash. This is ideal for passwords because you want to store an obfuscated version of the user's password that you can easily compare at login without actually storing what the password is. This means if the database is compromised, so long as the passwords were hashed, the attacker wouldn't get the passwords, they would get the hashed passwords which are essentially useless (you can use rainbow tables and I'm sure other techniques to get the resulting hashes, but it takes a decent amount of effort).

This leads into your original question. Why are password hashes slow? They are slow because one of the only ways to get the original string from a hash is to re-generate that hash. So if it takes 1 second to generate each hash it becomes a bigger time sink than it would have been had you used a fast hash such as md5 of a version of sha. Fast hashes are great for pretty much everything except for password storage.

Hopefully this answers your question. Just as an aside, I would strongly recommend generating a unique salt for each user and passing that in as one of the options into password_hash. This salt can be stored as plain-text in the database alongside the hashed password. Using a different salt for each password will add that into the password so a would-be attacker would have to generate a rainbow table for every salt that's in the database. At this point the attacker would likely utilize other techniques to get the passwords instead of a database breach.