8

Okta released a security advisory 4 days ago, stating that accounts with username longer than 52 characters can login with arbitrary password under specific conditions. Some people in X/Twitter suspect that Okta use userid + username + password as the input of bcrypt.

If this is true, then the password would be ignored by bcrypt given a long enough userid + username.

A commenter suggested pre-hashing the input with SHA256 before applying bcrypt, thus avoiding the length limit problem. This pre-hash method is also mentioned (and generally approved) in some answers in this site (1,2).

However, the OWASP cheatsheet explicitly discourage this:

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 and other issues when combining bcrypt with other hash functions.

I am not a security expert, so I am looking for professional opinions:

  1. Should I strictly avoid pre-hashing bcrypt input with another hash function, as mentioned in the OWASP cheatsheet?
  2. Would I be in serious trouble (e.g. passwords easily cracked) if I pre-hash bcrypt input with SHA256?
  3. Someone on Reddit suggested that salting the sha256 hash BEFORE applying bcrypt can prevent password shucking (at least this is how I read it). Is this true?

Edit: I have a follow up question.

  1. This blog post (which is cited in the OWASP cheatsheet excerpt above) mentioned that it is safe if we encode the prehash first (with base64 or hex) before throwing it to bcrypt. I understand this is to eliminate null bytes from the raw output.

However, OWASP used bcrypt(base64(hmac-sha256(data:$password, key:$pepper)), $salt, $cost) as an example, which implies it is unsafe even if you encode the prehash output.

Are they in conflict whith each other? If yes, which one is correct?

3
  • 1
    If I interpret the password shucking video correctly, shucking only works with unsalted hashes (mostly in the situation of "upgrading" an existing password database). And the combination issues stem from the presence of null bytes in the raw output of another crypto function, which is not the case in the example given which uses base64. I think there's a problem in the wording of the OWASP page, it should warn of the issue and point to the right fix (like using the base64 version as described in the example).
    – jcaron
    Commented Nov 6 at 13:31
  • If I interpret this post ( scottbrady91.com/authentication/beware-of-password-shucking ) on password shucking correctly, it only works if the inner hash has leaked beforehand, and the attacker can just run the outer hash function to match against the final hash. But that doesn't really apply if you use bcrypt(somehash(pass)) from the get go, right?
    – ilkkachu
    Commented Nov 6 at 17:11
  • 1
    @ilkkachu: The inner hash somehash(pass) can leak through an entirely different application which isn’t under your control and which you may not even be aware of. So this is not just about old hashes in your own application. The only way to prevent the attack is use a parameterized inner construct like HMAC which accepts both a key/salt and the password, and to not hash existing hashes (since the current ones may have been leaked at some point).
    – Ja1024
    Commented Nov 6 at 19:05

2 Answers 2

6

Yes, you should avoid pre-hashing the password with a simple hash algorithm like SHA-256 due to password shucking. I also strongly recommend against trying to invent your own salting scheme like in the Reddit post.

There are three options.

  • Accept the input limitations of bcrypt and enforce them in your application. 72 bytes is quite a lot even for passphrases, and there's no good reason for allowing null bytes in text fields anyway. Sure, if non-ASCII characters are used, a single character can take up multiple bytes, but even then, the problem should be irrelevant in practice. I can assure you that the average user doesn't have passwords longer than 72 bytes. And experts understand that password strength is determined by entropy, not length, so they just have to use a sufficiently compact encoding. For example, 128 random bits encoded as a hexadeimal number requires only 32 digits, which is far below the limit.
  • Use a standardized construction like HMAC with a random key for each user to preprocess the password. The keys can be stored as plaintext (in which case they act as salts like in the HKDF key derivation function) or encrypted with a master key (if you additionally want a pepper). This allows you to solve the password-shucking without relying on any home-made schemes. Note that the HMAC output must be encoded, so that it's below the bcrypt limit and doesn't contain any null bytes.
  • Switch to a more modern password hash algorithm which doesn't have these limitations like Argon2, Balloon or scrypt.

To answer the additional questions:

The blog post only addresses the null-byte problem, not password shucking. My guess is that the author simply wasn't aware of that attack, since the blog post was published 5 years before the password-shucking presentation at DEF CON. So the encoding recommendations are correct, but simply using SHA-256 is not.

As to why the OWASP cheat sheet seems to recommend against HMAC, feel free to open an issue in their GitHub repository. This might be an error, because the code fragment doesn't match the text (which talks about SHA-256). Interestingly, the code originally showed SHA-384, but this was changed to HMAC in commit 658ba04, referencing “advice from twitter.com/AaronToponce” (whatever that advice was).

6
  • Thanks for your answer! I have updated my question because there seems to be conflicting information in OWASP cheatsheet. Could you answer my follow up question as well?
    – Reguna
    Commented Nov 6 at 1:54
  • 1
    @Reguna: I've updated the answer. For the OWASP-related question, it's probably best to ask them directly, either by opening an issue in their repository or sending an e-mail to the commit author.
    – Ja1024
    Commented Nov 6 at 2:46
  • I have opened an issue here: github.com/OWASP/CheatSheetSeries/issues/1532
    – Reguna
    Commented Nov 6 at 8:20
  • @Reguna: I've already answered your questions you've posted in the issue, and this is not what the conflict is about. The OWASP does not say you should follow the advice in the blog post. It uses the link to point to “other issues when combining bcrypt with other hash functions” besides password shucking. The blog post then talks about the null-byte issue and tries to solve this with encoding. However, the blog author most definitely wasn't aware of password shucking, so his advice to just encode the inner SHA-256 hash is obsolete.
    – Ja1024
    Commented Nov 6 at 11:26
  • 1
    @Reguna: In contrast, the OWASP is aware of password shucking, so they completely recommend against pre-hashing, even if the pre-hash is encoded. That's not a conflict in the OWASP article. However, there is a conflict when the OWASP recommends against pre-hashing with SHA-256 but then shows HMAC as the inner function, because SHA-256 and HMAC are two entirely different things. I hope this is more clear now.
    – Ja1024
    Commented Nov 6 at 11:35
4

There's some discussion about using salts to protect against password shucking in this question that would be worth a read.

So yes, you can protect against it by using a salt. However, you need to make sure that you're applying the salt to the password rather then the hash. That is, your construct should be bcrypt(sha256($password.$salt)) , and not bcrypt(sha256($password).$salt)

So if you're correctly applying a sufficiently long and unique salt, and your implementation doesn't have any issues with things like null bytes in the sha256 output (and you're confident that will never change), then there's nothing strictly wrong with pre-hashing the password before you bcrypt it.

But assuming that you're just hashing the user's password (not doing something weird concatenating it onto the end of the username/ID as you suggested in your post) then I'd question whether you're really gaining anything over just enforcing a 72 byte input limit (or whatever your implementation supports).

And if you're concerned about that input length limit, then you'd probably be better off migrating to argon2id (which has no practical input length limit), rather than adding complexity by trying to work around the limitations of bcrypt.


Edit to response to your new question: base64 encoding your pre-hash will solve the issue with null bytes causing problems for the bcrypt function. But depending on your specific implementation that might not have been an issue for you in the first place (as the sha256() function in some libraries will default to hex or base64 encoded output anyway).

As to the question of which is correct, I think the answer can be "both.

Think of pre-hashing with bcrypt as a footgun (or more generally, you can think of bcrypt as one). If you're very careful and understand exactly what you're doing, then you can absolutely implement it in a secure way. If you get it wrong (as OKTA may have), you blow your own foot off.

Which means for a project like the OWASP Cheat Sheet Series, which is aimed to providing "a concise collection of high value information on specific application security topics" to an audience of non-experts, the simple recommendation is "don't do this".

4
  • 1
    Do you have any peer-reviewed reference like an RFC or a NIST document for the sha256(password + salt) construction? This may seem like a trivial operation, but this answer indicates that it's not necessarily secure. And length-extension attacks have shown that even simple constructions which look perfectly fine can be insecure.
    – Ja1024
    Commented Nov 5 at 19:59
  • Thanks for your answer! I have updated my question because there seems to be conflicting information in OWASP cheatsheet. Could you answer my follow up question as well?
    – Reguna
    Commented Nov 6 at 1:55
  • 1
    @Reguna I don't think that those two things are necessarily in conflict, as I've said in my update.
    – Gh0stFish
    Commented Nov 6 at 10:05
  • Thanks for your updated answer!
    – Reguna
    Commented Nov 6 at 11:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .