20

I would like to allow users of my web application to have long passwords, if they so wish. Today I became aware of bcrypt's password length limitation (72 characters, the rest truncated).

Would it be secure for me to do the following? I am using PHP.

Current Implementation:

password_hash($password, PASSWORD_BCRYPT, $options);

Implementation in question:

password_hash(hash('sha256', $password), PASSWORD_BCRYPT, $option);

What are the drawbacks of the implementation in question?

I am not a crypto expert, please advise.

Will the implementation in question limit the password length that a user can use? If so, what will the limit be?

David Foerster
  • 580
  • 4
  • 10
Jay
  • 405
  • 4
  • 12
  • 7
    See also [Is there a way to use bcrypt with passwords longer than 72 bytes securely?](http://crypto.stackexchange.com/questions/24993/is-there-a-way-to-use-bcrypt-with-passwords-longer-than-72-bytes-securely) – Sjoerd Feb 14 '17 at 10:13
  • 1
    Related and probably dublicate: [What are the pros and cons of using sha256 to hash a password before passing it to bcrypt?](https://security.stackexchange.com/questions/92175/what-are-the-pros-and-cons-of-using-sha256-to-hash-a-password-before-passing-it?rq=1) – Ferrybig Feb 14 '17 at 17:14
  • Related and also probably duplicate: http://security.stackexchange.com/q/93730/5220 – ratchet freak Feb 15 '17 at 12:08

3 Answers3

24

In general, this will be fine. It's even a fairly well recommended method to allow arbitrary length passwords in bcrypt.

Mathematically, you are now using bcrypt on a 64 character string, where there are 2^256 possible values (SHA-256 gives 256 bit output, which is commonly represented as a 64 character hexadecimal string). Even if someone pre-calculated common passwords in SHA-256, they'd need to run those through bcrypt to find what the actual input for a given hash was.

The main potential drawback is implementation flaws: if you ever store the SHA-256 values, they're relatively fast to break, so an attacker wouldn't need to expend the effort to break the bcrypt values.

It would still be recommended to keep a high enough iteration count for the bcrypt step - this shouldn't have any particularly detrimental effect on your processing time, but makes brute force attacks much harder.

See Pre-hash password before applying bcrypt to avoid restricting password length for the general case.

Matthew
  • 27,233
  • 7
  • 87
  • 101
  • Thanks a lot! When it then comes to user login, do I just perform the usual password_verify($posted_password, $db_stored_hash), or would be I correct in assuming that I should throw an SHA-256 hash into the verification process too? So I am thinking it should be password_verify(hash('sha256', $posted_password), $db_stored_hash), is thinking correct? – Jay Feb 14 '17 at 10:19
  • 1
    You'd need the hash in there, yes. `password_verify` will just compare the input and bcrypt hash, so the input to it needs to be whatever was hashed at that point. – Matthew Feb 14 '17 at 10:29
  • 1
    Salt all the things! :) – Sten Petrov Feb 14 '17 at 19:31
  • 2
    @StenPetrov Oddly enough, you don't need to add any more salt to that which bcrypt provides - the purpose is to mask identical passwords, which bcrypt's salt does perfectly well! No harm in adding it to the SHA-256 hash as well, but you don't need to. – Matthew Feb 14 '17 at 19:40
  • 1
    @Matthew yes, I was referring to SHA, for the reason you pointed out - precomputed SHA hashes... in this case the added protection wouldn't be very significant since SHA is fast but I'd add it nevertheless – Sten Petrov Feb 14 '17 at 22:22
12

Your suggested implementation is safe. However take care to document your special usage of the function.

Please note that password_hash will truncate the password at the first NULL-byte so you must NOT use the option $raw_output=true of the hash function.

A. Hersean
  • 10,046
  • 3
  • 28
  • 42
4

Don't do it, you are limiting your key strength to much less than what bcrypt alone can accept.

Use this approach instead (pseudo-code not valid PHP):

if length($password) > 72 bytes
then
    $hash = sha256($password)
    $password = concat(substring($password, 320 bits), base255encode($hash))
end if
bcrypt($password)

In this way, passwords between 256 and 576 bits retain their full entropy, and passwords of above 576 bits use not only the 256 bits from the hash, but as much of the original entropy as can fit alongside the hash.

It's debatable whether the hash should be done across the whole password, or just the portion beyond 320 bits that the hash replaces. I seriously doubt it makes any difference when you've chosen a cryptographic hash with good properties.

The base255encode step avoids having a NUL byte terminate the key early, as @a-hersean warned about.

Ben Voigt
  • 760
  • 1
  • 10
  • 17
  • This seems unnecessarily complicated. Are 256 bits not enough? – Ry- Feb 15 '17 at 02:16
  • 4
    256 bits are not enough for someone paranoid enough to use a password longer than 72 bytes. – Ben Voigt Feb 15 '17 at 02:26
  • Well, they don’t have to know… – Ry- Feb 15 '17 at 02:35
  • @BenVoigt I agree with the principle of your comment. But you could well have a >72B password and a non-paranoid user. If you consider a sentence style password with a few unicode chars thrown in (e,g, the sentence is in a language that doesn't work in ASCII) you could actually hit 72B. – Chris H Feb 15 '17 at 09:31
  • 2
    If you want more than 256 bits, why not just use `base255encode(hash('sha512', password, true))` instead of this complicated scheme above? As the entropy of the password is not guaranteed to be spread evenly across the individual characters, taking an arbitrary substring doesn't seem very safe to me. – wrtlprnft Feb 15 '17 at 12:21
  • @wrtlprnft: Taking an arbitrary substring is completely safe, because the information content of that substring is never less than the mutual information between it and the hash, and therefore inclusion of the substring can never decrease the total entropy. It might not be optimal, but it is assuredly safe. Using a hash with a larger output is a good idea too, but dodges the question asked, which is specifically about SHA256. – Ben Voigt Feb 15 '17 at 16:11
  • @BenVoigt: Of course, it is safe in the sense that it never reduces the entropy beyond 256 bits. But if the goal is to allow more than 256 bits of entopy, I would expect it to work for all kinds of passwords. For example, a random 512-character string of `'a'` and `'b'` would perform poorly with this algorithm's method. You could concatenate two sha256 hashes if you insist on using sha256: `sha256($password) . sha256($passwort . $some_constant)` – wrtlprnft Feb 15 '17 at 17:05
  • Your math is just plain wrong. Bcrypt's output is limited to 184 bits of entropy. This solution is overly complex without adding any benefit. Remember, [do not roll your own crypto](http://security.stackexchange.com/q/18197/127837). – A. Hersean Feb 17 '17 at 12:56
  • I now believe this and the various linked questions should be closed as duplicates of http://crypto.stackexchange.com/q/32341 – Ben Voigt Feb 17 '17 at 14:48