Skip to content

Commit

Permalink
Updated the bcrypt description as discussed in #1532
Browse files Browse the repository at this point in the history
In #1532 there was a side discussion by @ja2048  and @jmanico on why
the CS mentioned that HMAC would be vulnerable to password shucking,
but the linked talk mentioned that even a hash with a pepper would
prevent it. So I correcteed the guidance.

I further removed the mentioning of PBKDF2, since this suggests that
bcrypt + PBKDF2 would give FIPS-140 compliance.

I also added that bcrypt should only be used for legacy system which
don't have agron2 or scrypt, as discussed in #1532
  • Loading branch information
raphaelahrens committed Nov 28, 2024
1 parent 6920eb3 commit f3775e6
Showing 1 changed file with 15 additions and 2 deletions.
17 changes: 15 additions & 2 deletions cheatsheets/Password_Storage_Cheat_Sheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ These configuration settings provide an equal level of defense. The only differe

### bcrypt

The [bcrypt](https://en.wikipedia.org/wiki/bcrypt) password hashing function should be the best choice for password storage in legacy systems or if PBKDF2 is required to achieve FIPS-140 compliance.
The [bcrypt](https://en.wikipedia.org/wiki/bcrypt) password hashing function *should only* be used for password storage in legacy systems where Argon2 and scrypt are not available.

The work factor should be as large as verification server performance will allow, with a minimum of 10.

Expand All @@ -129,7 +129,20 @@ bcrypt has a maximum length input length of 72 bytes [for most implementations](

#### Pre-Hashing Passwords with bcrypt

An alternative approach is to pre-hash the user-supplied password with a fast algorithm such as SHA-256, and then to hash the resulting hash with bcrypt (i.e., `bcrypt(base64(hmac-sha256(data:$password, key:$pepper)), $salt, $cost)`). This is a dangerous (but common) practice that **should be avoided** due to [password shucking](https://www.youtube.com/watch?v=OQD3qDYMyYQ) and other issues when [combining bcrypt with other hash functions](https://blog.ircmaxell.com/2015/03/security-issue-combining-bcrypt-with.html) (**Note:** "The Fix" in this blog post only protect you from null bytes; it does **NOT** protect you from password shucking!).
An alternative approach is to pre-hash the user-supplied password with a fast algorithm such as SHA-2, HMAC, or BLAKE3 and then to hash the resulting hash value with bcrypt (i.e., `bcrypt(H($password)), $salt, $cost)`)..
This can be **dangerous** because of null bytes in the hash output value and because of [password shucking](https://www.youtube.com/watch?v=OQD3qDYMyYQ).

The original bcrypt expects a null terminated password string, this means that the hash value will only be used to the first null byte in the hash value. (`bcrypt(H($password)), $salt, $cost) == bcrypt("", $salt, $cost)` if `H($password)[0] == 0`)
This increases the chance of finding a collision when [combining bcrypt with other hash functions](https://blog.ircmaxell.com/2015/03/security-issue-combining-bcrypt-with.html) and can be avoided by encoding the hash value to printable string with something like base64.
While base64 increases the length of the hash value above 72 characters and so there is a bit of truncation for large hash values from hashes like SHA-512 this is [negligible](https://soatok.blog/2024/11/27/beyond-bcrypt/).

Password shucking uses the fact, that it is easy to check if `bcrypt(base64(H($password))), $salt, $cost) == bcrypt(base64($leaked_hash), $salt, $cost)`.
If the inner hash function `H` is used with the same password somewhere else and known to an attacker cracking the password can be reduced to breaking the hash function `H`.
Just using pure SHA-512, ( i.e. `bcrypt(base64(sha512($password))), $salt, $cost)`) is a **dangerous practice** and is as secure as just using pure SHA-512.
Password shucking only works if a leaked hash is known to the attacker, either through a breach database or rainbow tables.
To mitigate password shucking a [pepper](#peppering) can be used.

To summarize if bcrypt has to be used and the password should to be pre-hashed you should do `bcrypt(base64(hmac-sha384(data:$password, key:$pepper)), $salt, $cost)` and store the pepper not in the database.

### PBKDF2

Expand Down

0 comments on commit f3775e6

Please sign in to comment.