|
|
||
|---|---|---|
| bcrypt.md | ||
| md5.md | ||
| pbkdf2.md | ||
| plaintext.md | ||
| README.md | ||
| sha1.md | ||
PHP hash "collisions"
Register with password 1 and then sign in with password 2. If you're in then the storage uses specified algorithm to hash the password and PHP uses == to compare them (for MD5, SHA-1, and plaintext).
MD5 and SHA-1
For MD5 and SHA-1, it uses the long-known trick (it actually is a documented feature, see PHP type comparison tables & Floating point numbers) that for PHP '0e1' == '00e2' == '0', it just uses it for practical purposes. Any password matches any other password from the list. This is a different trick than integral strings overflowing into floating point numbers, just spot the difference between these two lines.
Plaintext
For plaintext, it uses various conversion tricks. First password will match just the second one. Tricks are grouped by PHP versions allowing them.
bcrypt
bcrypt truncates passwords to a maximum length of 72 characters. The passwords match if the first 72 characters of both passwords match.
PBKDF2
If you use a password longer than 64 bytes and hash it with PBKDF2-HMAC-SHA1, it is first pre-hashed with SHA1, so PBKDF2-HMAC-SHA1(password1) === PBKDF2-HMAC-SHA1(password2) because sha1(password1) === bin2hex(password2).
Conclusion
Use === when comparing anything in PHP, not ==. And use password_hash() and password_verify() for password hashing in PHP, don't use MD5 or SHA-1.
History
It all started with this tweet, I've generated QNKCDZO and 240610708 in February 2014 and it has since spread all over the intertubes. Just google it.
- MD5: Tweet, code
- SHA-1: Tweet, code
- Plaintext: Tweet, code
- bcrypt: code
- PBKDF2: Tweet by Christian "CodesInChaos" Winnerlein, as explained by Mathias Bynens, code