I observed a very strange behavior with Laravel's Hash
Facade using Hash::make()
to create a digest (with bcrypt
) and save it to the database. For example, the plain text
AAMkAGEzN2EyZTg4LWRiNTUtNGIwYS04ZTA1LWE2Y2U5OTRjYjQ0ZgBGAAAAAACxCzc14g3eSoadAxaGpB3ABwCr5qkyxHH4QY9vHKr6u5IrAAAAAAENAACr5qkyxHH4QY9vHKr6u5IrAARi2BmGAAA=
yields $2y$10$fq6jvoNL/RShVKfNDy64EOGW0gLzd0GvfS.di16Z9LcCK7DpIHONK
.
Now, when using Hash::check()
with the plain text and digest mentioned above returns true
of course. However, changing one character in the plain text (e.g. replacing the last A
with a B
) and checking it against the same digest returns true
as well:
>>> Hash::check('AAMkAGEzN2EyZTg4LWRiNTUtNGIwYS04ZTA1LWE2Y2U5OTRjYjQ0ZgBGAAAAAACxCzc14g3eSoadAxaGpB3ABwCr5qkyxHH4QY9vHKr6u5IrAAAAAAENAACr5qkyxHH4QY9vHKr6u5IrAARi2BmGAAA=', '$2y$10$fq6jvoNL/RShVKfNDy64EOGW0gLzd0GvfS.di16Z9LcCK7DpIHONK')
=> true
>>> Hash::check('AAMkAGEzN2EyZTg4LWRiNTUtNGIwYS04ZTA1LWE2Y2U5OTRjYjQ0ZgBGAAAAAACxCzc14g3eSoadAxaGpB3ABwCr5qkyxHH4QY9vHKr6u5IrAAAAAAENAACr5qkyxHH4QY9vHKr6u5IrAARi2BmGAAB=', '$2y$10$fq6jvoNL/RShVKfNDy64EOGW0gLzd0GvfS.di16Z9LcCK7DpIHONK')
=> true
>>> Hash::check('AAMkAGEzN2EyZTg4LWRiNTUtNGIwYS04ZTA1LWE2Y2U5OTRjYjQ0ZgBGAAAAAACxCzc14g3eSoadAxaGpB3ABwCr5qkyxHH4QY9vHKr6u5IrAAAAAAENAACr5qkyxHH4QY9vHKr6u5IrAARi2BmGAAC=', '$2y$10$fq6jvoNL/RShVKfNDy64EOGW0gLzd0GvfS.di16Z9LcCK7DpIHONK')
=> true
Based on my understanding what hashing does this shouldn't be possible, but it doesn't seem to be a collision as replacing B
by C
also yields true
.
I'm using Laravel 8.0 with PHP 7.4.11.
Any idea what I'm doing wrong here?
UPDATE:
Found this hint in the official PHP documentation for password_hash
:
Caution: Using the PASSWORD_BCRYPT as the algorithm, will result in the password parameter being truncated to a maximum length of 72 characters.
I then checked this and indeed, modifying any of the characters behind AAMkAGEzN2EyZTg4LWRiNTUtNGIwYS04ZTA1LWE2Y2U5OTRjYjQ0ZgBGAAAAAACxCzc14g3e
doesn't change the result whereas exchanging e.g. the last e
with f
returns false
for Hash::check()
. The length of the string is 72
characters so it may be an effect of the truncation. But why? This isn't mentioned in the Laravel Hash
documentation. I have several passwords that are longer than 72 characters so it actually doesn't matter how they end?
As a result, I need to use another function of Laravel to hash longer messages? Which one?