Hacker News new | ask | show | jobs
by atoponce 2940 days ago
> Isn't the reason this is true that bcrypt just truncates the input at 72 characters? And if that's correct, aren't there basically a few options, any of which basically invalidate the argument?

Let's run a scenario to show by bcrypt from strictly a CPU load perspective is better than sha256/512crypt.

> 1. Enforce a password length limit. Fixes the unbounded growth of the shacrypt CPU time while simultaneously ensuring bcrypt is not ignoring input.

Assume that I know how many authentications per second are happening on my busy authentication server, and I can reliably guarantee 0.25 seconds per request without creating an outage. On my hardware, that's a bcrypt cost of 12. If I pre-hash every password with SHA-256, then base64 encode the hash, and send that to bcrypt, I can guarantee every user any arbitrary length of password, and know that it will not take more than 0.25 seconds.

For sha256crypt, a cost of 250,000 rounds is similar in performance for an 8-character password, executing in about 0.250158 seconds. However, and 36-character Diceware passphrase takes about 0.353884 seconds, and grows from there (70 characters at 0.503353 seconds, etc). Obviously, I can't allow everyone to have 36-character Diceware passphrases with a sha256crypt cost of 250,000 rounds. It would create load problems on my hardware.

I have three options that I see ahead of me if I wanted to stick with sha256crypt:

1. I could decrease the sha256crypt rounds, so those who want longer passwords won't create a CPU outage due to load.

2. Enforce maximum password length limit of 35 characters, to prevent the execution time from taking too long.

3. Make some exceptions in the code, so those with 1-10 characters have a higher sha256crypt cost than those with 11-24, which is still higher than those with 25-36, etc.

If I just stick bcrypt on the server with a cost of 12, I don't have handle these exceptions.

> 2. Pre-hash the input to bcrypt by passing it through SHA512. Makes bcrypt now grow in the exact same way as shacrypt.

No it doesn't. In the graphs on my post, the bcrypt timings are based on https://passlib.readthedocs.io/en/stable/lib/passlib.hash.bc.... bcrypt only grows when the cost is increased. Its execution times are in no way affected by password length.

> 3. Don't enforce a password limit, but truncate the password. Fixes the CPU time.

This is a bad design, and falls back to the security vulnerabilities of DES-Crypt and LM hashes.

> Passing 4096 bytes to bcrypt is the same as passing 72 bytes to bcrypt because it ignores the last 4024 bytes right?

Unless you bcrypt(base64(sha-256(password))), which is a common approach.

1 comments

> Unless you bcrypt(base64(sha-256(password))), which is a common approach.

If we acknowledge that pre-hashing is acceptable (and almost certainly more secure) for bcrypt, why can't we allow the same for shacrypt? If we do, all of the performance issues about shacrypt go away. You now have a fixed-length password.

shacrypt(sha-256(password))

bcrypt is always fast because it truncates to 72 characters. Because it truncates, you should pre-hash. shacrypt is slow for big inputs because it doesn't truncate. Because it is slow, you should pre-hash. If we accept both of these, there's really no difference in bcrypt and shacrypt is my point. I don't understand why you allow it as an option for bcrypt but not shacrypt.

The sha256/512crypt algorithms, as well as md5crypt, hash the input based on each character in the password. This is why the execution time increases. bcrypt does not do this.

bcrypt is constant-time, not because it truncates at 72 characters. It's constant-time, because it slurps the password and salt once into the state (initial setup), then spends the rest of the time manipulating that state based on the cost (expand key).

Yes, you could pre-hash with sha256crypt or sha512crypt, and that completely works around the password-length performance issue. You pre-hash with bcrypt to get around a maximum length restriction, not to get around a performance bottleneck.

> bcrypt is constant-time, not because it truncates at 72 characters. It's constant-time, because it slurps the password and salt once into the state (initial setup)

And why do you think that initial setup is constant-time? Because it truncates at 72 characters! Otherwise it would be O(n), and so bcrypt overall would be O(n) + O(m) (if n is password length and m is cost factor).

It's quite simply impossible to have any hash function not be at least O(n) without truncation. That would imply that the data does not even need to be read to compute the hash.

Your point is still relevant in that without truncation, shacrypt would be O(n*m) and bcrypt would be O(n) + O(m), but NEITHER is O(m) without truncation. If shacrypt truncated, it would be O(m), just like bcrypt is O(m) with truncation. If both prehash instead of truncate, then both are O(n) + O(m).

From my testing with Python's base64, hashlib, and passlib.hash modules, SHA-256 prehashing and base64 encoding is only approximately one one-hundredths the total execution time of bcrypt_sha256 for a 4 KB "password". This is why slurping in the password during the initial state isn't noticeably adding to the overall execution time of password prehashing with bcrypt, and it is analogous to why PBKDF2, scrypt, and Argon2 all show constant time in execution regardless of password length- slurping in the password during initialization is a fraction of the execution time.