How to compare password hashes in PHP?
posted May 2015
The wierdness of ==
Do you know what happens when you run this code in PHP?
<?php var_dump(md5('240610708') == md5('QNKCDZO')); var_dump(md5('aabg7XSs') == md5('aabC9RqS')); var_dump(sha1('aaroZmOk') == sha1('aaK1STfY')); var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m')); var_dump('0010e2' == '1e3'); var_dump('0x1234Ab' == '1193131'); var_dump('0xABCdef' == ' 0xABCdef'); ?>
Check the answer here. That's right, everything is True.
This is because
== doesn't check for type, if a string looks like an integer it will first try to convert it to an integer first and then compare it.
This is weird and you should use
Even better, you can use hash_equals (coupled with
Compares two strings using the same time whether they're equal or not.
This function should be used to mitigate timing attacks; for instance, when testing crypt() password hashes.
Here's the example from php.net:
<?php $expected = crypt('12345', '$2a$07$usesomesillystringforsalt$'); $correct = crypt('12345', '$2a$07$usesomesillystringforsalt$'); $incorrect = crypt('apple', '$2a$07$usesomesillystringforsalt$'); hash_equals($expected, $correct); ?>
Which will return
the hashed strings start with
0e, for example both strings are equals in php:
md5('240610708') = 0e462097431906509019562988736854 md5('QNKCDZO') = 0e830400451993494058024219903391
because php understands them as both being zero to the power something big. So zero.
Now, if you're comparing unencrypted or unhashed strings and one of them is supposed to be secret, you might have potentialy created the setup for a timing-attack.
Always try to compare hashes instead of the plaintext!