Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PBKDF2 polyfill vulnerable to "long password DoS" #230

Closed
timoh6 opened this issue Apr 13, 2016 · 19 comments
Closed

PBKDF2 polyfill vulnerable to "long password DoS" #230

timoh6 opened this issue Apr 13, 2016 · 19 comments

Comments

@timoh6
Copy link

timoh6 commented Apr 13, 2016

The PBKDF2 polyfill in Core.php (which should run only for PHP 5.4 users) doesn't pre-hash the password before PBKDF2 inner loop if the password's length is greater than the underlying hash algorithm's output length.

This could lead to DoS situation if a malicious user is able to input long passwords to the PBKDF2 function. I.e. on my machine, 100000 byte password and 25000 iterations (with SHA256 and 32 byte output) took ~16 seconds.

I suggest we add a conditional pre-hash step before the outer PBKDF2 loop to mitigate this issue: check the password lenght, and if it is greater than $hash_length, do a pre-hash.

Roughly something like:

if (Core::ourStrlen($password) > $hash_length) {
    $password = \hash($algorithm, $password, true);
}
@defuse
Copy link
Owner

defuse commented Apr 13, 2016

Wouldn't this then not actually implement PBKDF2? An alternative is to throw an exception if the password is longer than 1000 characters (or some small factor of $hash_length). What do you think?

@defuse
Copy link
Owner

defuse commented Apr 13, 2016

Thanks for looking, by the way!

@defuse defuse assigned defuse and unassigned defuse Apr 13, 2016
@defuse defuse added the v2.0 label Apr 13, 2016
@defuse
Copy link
Owner

defuse commented Apr 13, 2016

Related:

Let's see what Django (and other things) did, and if it's pre-hashing, I'm fine with deviating from PBKDF2 spec as long as we rename the function to indicate we did.

@defuse
Copy link
Owner

defuse commented Apr 13, 2016

Django just fails if it's longer than 4096 bytes: https://www.djangoproject.com/weblog/2013/sep/15/security/

@defuse
Copy link
Owner

defuse commented Apr 13, 2016

Proposed fix:

  1. Don't modify the PBKDF2 code, keep it standards-compliant, but add a warning comment about the DoS vector.
  2. Change our KDF in KeyOrPassword to always hash the password before giving it to PBKDF2 (even short passwords).

What do you think of that @timoh6?

@defuse
Copy link
Owner

defuse commented Apr 13, 2016

I wonder what PHP's implementation of hash_pbkdf2 does for long passwords? If it does something special like pre-hashing long passwords then we need to make sure the polyfill behaves the same, otherwise upgrading will break ciphertexts.

@paragonie-scott
Copy link
Collaborator

If you put the pre-hash logic here: https://github.com/defuse/php-encryption/blob/v2.0/src/Core.php#L398

...then it won't break anything.

@defuse
Copy link
Owner

defuse commented Apr 13, 2016

@paragonie-scott: Yeah, I mean, is PHP already doing something like this, in which case the polyfill is already broken because it's not doing the exact same thing? Or is PHP computing the DoS-vulnerable but standards-compliant version of PBKDF2?

@defuse
Copy link
Owner

defuse commented Apr 13, 2016

@paragonie-scott: That's just standard HMAC isn't it?

@defuse
Copy link
Owner

defuse commented Apr 13, 2016

@paragonie-scott: Oh, I see. $password is the key argument to HMAC, so if php has a way to do streaming HMAC with state copying (I think we do that in File) then we can eliminate the DoS without giving up standards-compliance.

@timoh6
Copy link
Author

timoh6 commented Apr 13, 2016

@defuse I'd go with the option 2 (pre-hash). But on the other hand, I don't think there will be any downsides if we just modify the PBKDF2 function to pre-hash if the input is longer than allowed in HMAC.

It is a simple fix that "just works".

In addition to that, some sort of "upper bound" to the allowed password length sounds indeed a good idea. Maybe that 4096 bytes will do?

@paragonie-scott
Copy link
Collaborator

I'd like to not make it conditional. Otherwise you end up with situations like this: http://www.openwall.com/presentations/PHDays2014-Yescrypt/mgp00009.html

Or this: https://3v4l.org/39uX2

It's ugly. But deviating from the standards is probably the "wrong" solution.

@defuse
Copy link
Owner

defuse commented Apr 13, 2016

Yeah, a function called pbkdf2 should compute the function as specified by the standard. If we add prehashing it'll go in KeyOrPassword and will be an explicit step in the CryptoDetails.md docs.

@timoh6
Copy link
Author

timoh6 commented Apr 13, 2016

@paragonie-scott @defuse Good points. Pre-hashing in KeyOrPassword sounds reasonable to me.

@paragonie-scott
Copy link
Collaborator

@defuse That sounds great to me.

@defuse
Copy link
Owner

defuse commented Apr 18, 2016

I've decided on prehashing the password in KeyOrPassword and not adding any length restrictions.

@defuse
Copy link
Owner

defuse commented Apr 19, 2016

Needs review over in #234.

@defuse
Copy link
Owner

defuse commented Apr 19, 2016

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants