I'm trying to learn more about PHP security best practices, and I came across password_hash and the password_compat project by Anthony Ferrara. I think I understand how to implement it, but in testing, I came across a curious behavior that contradicts my novice understanding of password hashing.
If I save the result of the password_hash function into a MySQL database user record and then retrieve that hash for verification using password_verify, it works as expected. However, if I do the exact same thing, but rather than pull from the database, I just hard-code the password hash via a copy/paste from the database, the password_verify function fails.
The code is below:
// Get the Username and password hash from the MySQL database. GetPassTestuser routine returns an array where
// position[0][0] is the username and position[0][1] is the password hash.
$arrUser = GetPassTestuser("mike24");
echo("User Name: ".$arrUser[0][0]."<br/>");
echo("Password hash: ".$arrUser[0][1]."<br/>");
// Run password_verify with the password hash collected from the database. Compare it with the string "mytest"
// (This returns true in my tests).
if (password_verify("mytest",$arrUser[0][1])){
echo("Password verified");
} else {
echo("Password invalid");
}
echo("<hr>Now On to our second test...<br/>");
// Now run password_verify with a string representation directly copied/pasted from the database. This is
// being compared with "mytest", which in my mind should return a true value. But it doesn't and this test
// fails. Not sure why.
if (password_verify("mytest","$2y$10$S33h20qxHndErOoxJL.sceQtBQXtSWrHieBtFv59jwVwJuGeWwKgm")){ // String shown here is the same as value contained in $arrUser[0][1]
echo("2nd Test Password verified");
} else {
echo("2nd test Password invalid");
}
While this isn't something I'd do in real code, I'm just trying to understand what the difference is. Why does it work OK when I use the string variable that supposedly contains the exact same hash, but doesn't work when it's hard coded?
Thanks!
FROM PHP DOC
To specify a literal single quote, escape it with a backslash (). To specify a literal backslash, double it (\). All other instances of backslash will be treated as a literal backslash: this means that the other escape sequences you might be used to, such as or , will be output literally as specified rather than having any special meaning.
If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible to form a valid variable name. Enclose the variable name in curly braces to explicitly specify the end of the name.
Use single quotes
Replace
"$2y$10$S33h20qxHndErOoxJL.sceQtBQXtSWrHieBtFv59jwVwJuGeWwKgm"
With
'$2y$10$S33h20qxHndErOoxJL.sceQtBQXtSWrHieBtFv59jwVwJuGeWwKgm'
Or
"\$2y\$10\$S33h20qxHndErOoxJL.sceQtBQXtSWrHieBtFv59jwVwJuGeWwKgm"
Regarding my comment, I figured this would work better as an answer:
if (password_verify("mytest",'$2y$10$S33h20qxHndErOoxJL.sceQtBQXtSWrHieBtFv59jwVwJuGeWwKgm')){ // String shown here is the same as value contained in $arrUser[0][1]
echo("2nd Test Password verified");
} else {
echo("2nd test Password invalid");
}
Have a look at the Strings chapter in the PHP manual. In short, your hard-coded hash is a PHP string with variable interpolation:
"$2y$10$S33h20qxHndErOoxJL.sceQtBQXtSWrHieBtFv59jwVwJuGeWwKgm"
^ ^ ^
Since those variables don't exist, you'll end up with blank substrings a three notices. You should have seen the notices. If you didn't, please check your error reporting settings.
(Hint: use single quotes everywhere except where you do want variable interpolation.)