Security Tip: Increase Your bcrypt Rounds

[Tip#58] It's time to upgrade your bcrypt rounds to 12 (or higher)!

Security Tip: Increase Your bcrypt Rounds

Greetings friends! This week we’re venturing into the fun and complex world scratching the surface of password hashing, by looking at a very simple change you should make with your apps to increase security based off the two PRs I recently submitted to Laravel. If there is interest, I can dive into hashing in a future article, but for now we’re just going to tick off the basics.

Before we get into it, I am excited to let you know that I will be speaking at both Longhorn PHP in Austin TX, and Laracon AU in Sydney! Both are in November1 and both will be my first time speaking/attending these specific events2, so I’m super excited to be going to both and to meet everyone there! I’ll be doing a talk on Passkeys and a hacker workshop at Longhorn, and a refreshed version3 of “Th1nk Lik3 a H4cker” at Laracon. I believe there are still tickets available for both (AU is almost sold out), and if you’re there, come say hi!

⚠️ Worried about your security, or have some compliance targets coming up?
🕵️ Reach out to organise a Laravel Security Audit and Penetration Test.

Increase Your bcrypt Rounds

(I’ve provided some background, skip down if you just want the summary!)

bcrypt is a password hashing function used for protecting passwords within databases so they cannot be identified and used by someone who gains access to the raw hash. It is currently the default hashing algorithm within both Laravel and PHP itself, and at the time of writing, is the most secure option we have for hashing passwords4.

As a super quick overview, password hashing is used to protect passwords when stored on the server or in a database. When the newly created password is submitted, it is passed into the hashing function and the resulting hash is stored. Since this hash is a one-way operation, there is no way to extract the hashed password. When the user tries to login or a password needs to be checked, the raw value of the password - from user submission - is hashed and the two hashes are compared. If the hashes are the same, then the password must be the same.

Although there is no way to directly extract a hashed password, it is possible to guess hashed passwords through brute force, by generating millions/billions of passwords per second and comparing them to the hash. This process can crack simple/known passwords in seconds. More complicated passwords take more time, but as computing power increases, the time required to crack passwords goes down.

Therefore, as computing power increases, we need to add more security into our password hashing. This is done through the Rounds / Costs / Work Factor, which effectively slows down the hashing function. Therefore, if it takes longer to generate a password hash, then it takes longer to brute force password hashes, and for a sufficiently complex password this time to brute force becomes unfeasible in any realistic scenario.

PHP’s support of password hashing with bcrypt was introduced 11 years ago and started with the default of 10. Likewise, Laravel defaulted to bcrypt and 10 rounds. However as I just mentioned, computing power is always increasing and 10 rounds is no longer considered sufficient for password hashes. As such, PHP has an open RFC to upgrade the default from 10 to 11 or 125, and to follow their lead, we’ve done in the same in Laravel by bumping the default to 12.

UPDATE: The RFC passed and 12 was chosen as the new default rounds in PHP 8.4.

So… all of that to say we’ve increased the default bcrypt rounds from 10 to 12!

Laravel skeleton PR:
Laravel framework PR:

Since this value is defined in the config/hashing.php file, you need to find the `bcrypt.rounds` value and change it to 12 from 10.

'bcrypt' => [
    'rounds' => env('BCRYPT_ROUNDS', 12),

That’s it! 🙂

Password hashes are backwards compatible and auth systems should be configured to automatically rehash passwords when they encounter older hashes, so there is no risk of anything breaking.

So just a bit more theory: We’ve picked 12 because the benchmarks indicate the hash generation is still well under the 500ms point, at which point requests involving hashing may be noticeably slow and system performance may be affected. However, it’s worth pointing out that if you require a high level of security or have lots of resources available, you could increase your rounds to 13+. Just monitor the performance impacts and choose a level that works for you.

UPDATE: To clarify the question of “why now?”, the simple fact is that we need to be proactive about security. As computing increases, hash times decrease, and the easier it becomes to brute-force hashes. OWASP recommends “As a general rule, calculating a hash should take less than one second.”. Combine that with simple benchmarks that show 10 rounds taking less than 0.05 seconds to execute, compared with 12 sitting around 0.2-0.3 seconds and the security benefit become clear.

Looking to learn more?
Security Tip #39: Casing Request Values
▶️ In Depth #13: Stealing Password Tokens with Forwarded Host Poisoning

  1. It’ll be a busy month!

  2. Surprisingly, I haven’t been to Laracon AU before!

  3. My aim is to have a completely new set of challenges to the ones I featured at EU and US.

  4. There is occasionally talk of switching to Argon2, and it is often promoted as a better alternative to bcrypt, however there are some fundamental flaws in Argon2 that make it unsuitable for web applications. This question was raised in my PR and on the PHP internals mailing list, and one of the experts involved in choosing Argon2 has publicly said it was a mistake to select it.

  5. I’m hoping for 12, but the vote is very close right now! 🤞