0

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?

Johannes
  • 1,478
  • 1
  • 12
  • 28

1 Answers1

2

I did some research and came to this conclusion:

In the official laravel docs they refer to php's official docs. Here they have a caution section at password saying:

Using the PASSWORD_BCRYPT as the algorithm, will result in the password parameter being truncated to a maximum length of 72 characters.

I checked your string, and it has a length of 152. So in your case, you should use a different algorithm(you can set this in config/hashing.php at driver) or make sure the input string is max 72 characters.

Techno
  • 1,668
  • 1
  • 9
  • 19
  • Thanks. This accords with my findings I've added in the question. Do you know what is the reason for this decision? – Johannes Oct 26 '20 at 08:15
  • I looked around a bit, and I cannot find why it is 72. But in general limits are there to limit functionality so it does not take ages, or does not overflow memory. – Techno Oct 26 '20 at 11:39
  • Makes sense for me for the `password_hash` function but not for the `Hash` facade as hash functions are not only used for password hashing. At least a hint in the documentation would be fine, otherwise a lot of collissions will happen. I`ve added a note for this in the [Laravel docs GitHub](https://github.com/laravel/docs/pull/6525), hope it will get pulled and will help other developers to not waste so much time debugging... :D – Johannes Oct 26 '20 at 11:46
  • I mean the laravel docs refer to the php docs. So they do lead you to the correct info, you just have to dig up the info yourself, as is with most programming docs. If each and every doc needs to list each and every requirement, it would become a maintanance hell, as everyone has to update the same change in their docs. – Techno Oct 26 '20 at 13:06
  • Normally I would agree, but the link to the docs is in the section `Adjusting The Argon2 Work Factor`, so why should you visit the PHP documentation if you want to use the standard `bcrypt` driver? Of course somewhere in the manual of the car it is mentioned that you should retighten the wheel nuts after changing tires but who is actually reading the whole manual? IMHO this is a serious security thing that can be easily avoided by adding one sentence. – Johannes Oct 26 '20 at 13:11
  • 1
    See the first part of [this answer](https://crypto.stackexchange.com/a/25016/13022) for why this issue exists. @Johannes – Artjom B. Oct 26 '20 at 15:55
  • @ArtjomB. Thanks. Then SHA3 or another secure hash algorithm would be a better fit for a general-purpose hash facade right? – Johannes Oct 26 '20 at 16:00
  • Yes, you're right. But then everybody has a different definition of what SHA3 is ;) – Artjom B. Oct 26 '20 at 17:57
  • @ArtjomB. What do you mean? It's considered secure even for large amount of input data and is widely accepted as it's a standard. – Johannes Oct 27 '20 at 12:55