Why bcrypt Can Be Unsafe for Password Hashing ? | enamya

TL;DR: Bcrypt ignores any bytes after the first 72 bytes, due to Bcrypt being based on the Blowfish cipher which has this limitation.

Bcrypt has been a commonly used password hashing algorithm for decades, it’s slow by design, includes built-in salting, and has protected countless systems from brute-force attacks.

But despite its solid reputation, there are some hidden limitations to be aware of.

Let’s take a look at this code:

import bcrypt

password_1 = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"
password_2 = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2"
hashed_password1 = bcrypt.hashpw(password_1, bcrypt.gensalt())
if bcrypt.checkpw(password_2, hashed_password1):
    print("Good password")
else:
    print("Bad password")

The code takes a string (as bytes) starting with 72A ending with 1hashes it using bcrypt.hashpwand then checks the same string this time ending with 2 Against hashed passwords.

output should be Bad passwordCorrect ?

Let’s run the code, and see the output:

bcrypt.checkpw returns true

bcrypt.checkpw returns true

shows code Good passwordBut why ????

Turns out that Bcrypt is based on the Blowfish cipher, which only encrypts the first 72 bytes, so this limitation is inherited by Bcrypt.

The Blowfish algorithm uses an 18-element P-box, where each element is a 32-bit (4-byte) subkey. Therefore, the total P-box size is 18 * 4 bytes (72 bytes).

This means that if the password is longer than 72 bytes, the bcrypt algorithm will Encrypt only the first 72 bytesand the rest will be Ignored,

bcrypt’s 72-byte limit applies bytesNo LetterThis means that passwords containing non-ASCII characters (such as emoji or accented characters) can reach the limit even more quickly, since UTF-8 encoding can use more than 1 byte per character,

If you don’t want to worry about this limitation, you have several options:

  • Use a different algorithm, such as Argon2, which does not have this limitation (awarded a password hashing competition in 2015).

  • Hash the password into a digest –less than 72 bytes– First hash the digest using (SHA-256, SHA-512, etc.), and then bcrypt. See the following example:

Hash with SHA-512 before Bcrypt

Hash with SHA-512 before Bcrypt

  • You can always enforce the password length to be less than or equal to 72 bytes yourself, but it is not recommended to do so.

However, since version 5.0.0, Python’s bcrypt The package started causing errors when hashing passwords longer than 72 bytes, this commit introduces the change: raise ValueError If password is longer than 72 bytes

bcrypt.hashpw produces an error from version 5.0.0

bcrypt.hashpw produces an error from version 5.0.0

This limit is handled in different ways in other languages ​​and libraries.

  • Go throws an error when password is longer than 72 bytes
  • OpenBSD’s Bcrypt implementation truncates the password if it is longer than 72 bytes.
  • Rust’s bcrypt truncates passwords by default, but offers non_truncating ways to increase BcryptError::Truncation Error if password is longer than 72 bytes.
  • Spring Security base classes BCrypt provides method hashpw with for_check Hoist the flag (weird name, okay?) IllegalArgumentException If for_check = falseWhile I haven’t found a way to pass a flag similar to this BCryptPasswordEncoder Class.

In 2024, Okta – a major security service provider – had a security incident caused by this limitation, announcing that the incident was caused by using bcrypt to hash cache keys for their AD/LDAP Delegated Authentication feature, which allowed attackers to use new usernames with old cached keys to authenticate to the service and gain access to user accounts.

To summarize, bcrypt is still fine for normal passwords <72 bytesBut consider other options for future-proof security.


I found out about this limitation in @devhammed’s tweet, thanks to him for sharing this information.



Leave a Comment