note 124138 added to function.password-hash
| From: | phpnetcomment201908 at lucb1e dot com | Date: | Sun, 25 Aug 2019 14:17:06 +0000 |
| Subject: | note 124138 added to function.password-hash | ||
| Groups: | php.notes | ||
| Request: | Send a blank email to php-notes+get-212015@lists.php.net to get a copy of this message | ||
Since 2017, NIST recommends using a secret input when hashing memorized secrets such as passwords.
By mixing in a secret input (commonly called a "pepper"), one prevents an attacker from
brute-forcing the password hashes altogether, even if they have the hash and salt. For example, an
SQL injection typically affects only the database, not files on disk, so a pepper stored in a config
file would still be out of reach for the attacker. A pepper must be randomly generated once and can
be the same for all users. Many password leaks could have been made completely useless if site
owners had done this.
Since there is no pepper parameter for password_hash (even though Argon2 has a "secret"
parameter, PHP does not allow to set it), the correct way to mix in a pepper is to use hash_hmac().
The "add note" rules of php.net say I can't link external sites, so I can't back
any of this up with a link to NIST, Wikipedia, posts from the security stackexchange site that
explain the reasoning, or anything... You'll have to verify this manually. The code:
// config.conf
pepper=c1isvFdxMDdmjOlvxpecFw
<?php
// register.php
$pepper = getConfigVariable("pepper");
$pwd = $_POST['password'];
$pwd_peppered = hash_hmac("sha256", $pwd, $pepper);
$pwd_hashed = password_hash($pwd_peppered, PASSWORD_ARGON2ID);
add_user_to_database($username, $pwd_hashed);
?>
<?php
// login.php
$pepper = getConfigVariable("pepper");
$pwd = $_POST['password'];
$pwd_peppered = hash_hmac("sha256", $pwd, $pepper);
$pwd_hashed = get_pwd_from_db($username);
if (password_verify($pwd_peppered, $pwd_hashed)) {
echo "Password matches.";
}
else {
echo "Password incorrect.";
}
?>
Note that this code contains a timing attack that leaks whether the username exists. But my note was
over the length limit so I had to cut this paragraph out.
Also note that the pepper is useless if leaked or if it can be cracked. Consider how it might be
exposed, for example different methods of passing it to a docker container. Against cracking, use a
long randomly generated value (like in the example above), and change the pepper when you do a new
install with a clean user database. Changing the pepper for an existing database is the same as
changing other hashing parameters: you can either wrap the old value in a new one and layer the
hashing (more complex), you compute the new password hash whenever someone logs in (leaving old
users at risk, so this might be okay depending on what the reason is that you're upgrading).
Why does this work? Because an attacker does the following after stealing the database:
password_verify("a", $stolen_hash)
password_verify("b", $stolen_hash)
...
password_verify("z", $stolen_hash)
password_verify("aa", $stolen_hash)
etc.
(More realistically, they use a cracking dictionary, but in principle, the way to crack a password
hash is by guessing. That's why we use special algorithms: they are slower, so each verify()
operation will be slower, so they can try much fewer passwords per hour of cracking.)
Now what if you used that pepper? Now they need to do this:
password_verify(hmac_sha256("a", $secret), $stolen_hash)
Without that $secret (the pepper), they can't do this computation. They would have to do:
password_verify(hmac_sha256("a", "a"), $stolen_hash)
password_verify(hmac_sha256("a", "b"), $stolen_hash)
...
etc., until they found the correct pepper.
If your pepper contains 128 bits of entropy, and so long as hmac-sha256 remains secure (even MD5 is
technically secure for use in hmac: only its collision resistance is broken, but of course nobody
would use MD5 because more and more flaws are found), this would take more energy than the sun
outputs. In other words, it's currently impossible to crack a pepper that strong, even given a
known password and salt.
----
Server IP: 208.43.231.9
Probable Submitter: 82.199.157.54 (proxied: 181.23.200.69)
----
Manual Page -- http://php.net/manual/en/function.password-hash.php
Edit -- https://master.php.net/note/edit/124138
Del: integrated -- https://master.php.net/note/delete/124138/integrated
Del: useless -- https://master.php.net/note/delete/124138/useless
Del: bad code -- https://master.php.net/note/delete/124138/bad+code
Del: spam -- https://master.php.net/note/delete/124138/spam
Del: non-english -- https://master.php.net/note/delete/124138/non-english
Del: in docs -- https://master.php.net/note/delete/124138/in+docs
Del: other reasons-- https://master.php.net/note/delete/124138
Reject -- https://master.php.net/note/reject/124138
Search -- https://master.php.net/manage/user-notes.php