3

I consider the possibility of improving password security by increasing the hashing iteration count for short passwords.

The security benefits I see of this practice are:

  • Short passwords are better protected by making brute force more expensive.
  • Users are incentivized to use longer passwords.

Is this a good security practice?

Is this an already established practice with an official name?

The system I'd like to use this in already has a plugin architecture for password hashers, it does not have any centralized way to enforce a password policy and I might not have the mandate to enforce a password policy.

I have considered possible drawbacks of this approach and for each potential drawback I could think of I found reasons why it isn't a problem.

Potential leak of password length: Since the password database only stores the minimum iteration count used for longer passwords and the effective iteration count is calculated on the fly, the database cannot leak any information about password length. Timing attacks aimed at deducing the password length will not work either. An attacker attempting to brute force passwords will see the time it takes to validate a password vary depending on the length of the password the attacker attempts not the user's actual password. That means there is no way an attacker performing such an attack would learn the length of the user's password.

Potential DoS attacks: The increase in CPU consumption to validate passwords could be a DoS attack vector. But this attack vector already exists even when the iteration count is a fixed number high enough to match common recommendations. And the way to defend against this kind of DoS attack is to rate limit password attempts per client IP range. This defense against that class of DoS attacks will still work assuming one apply a minor tweak to adjust the permitted rate according to CPU consumption.

User experience: The slowdown in login could be argued to be bad user experience. I'd rather phrase it as incentivizing users to use longer passwords and call it a feature. At login time I could let users know that they can get faster logins by using a longer password. In particular if this could be a widespread practice it may give users the expectancy that longer passwords are faster, which would be a win to everybody.

From my considerations this far I have only found advantages to this idea. But I understand that there may be drawbacks which I may have missed. Are there reasons why one should not make iteration count higher for short passwords?

In order to avoid any ambiguity of what the idea I have in mind is, I have written an implementation of this idea in the Django framework. The only functional difference between the builtin PBKDF2PasswordHasher and my modification is these two lines:

    if len(password) < recommended_length:
        iterations *= progression_factor**(recommended_length-len(password))

The full implementation looks like this:

import base64
import hashlib

from django.contrib.auth.hashers import PBKDF2PasswordHasher
from django.utils.crypto import pbkdf2


def progressive_pbkdf2(password, salt, iterations, digest,
                       recommended_length, progression_factor):
    if len(password) < recommended_length:
        iterations *= progression_factor**(recommended_length-len(password))
    return pbkdf2(password, salt, iterations, digest=digest)


class ProgressivePBKDF2PasswordHasher(PBKDF2PasswordHasher):
    algorithm = "progressive_10_2_pbkdf2_sha256"

    def encode(self, password, salt, iterations=None):
        assert password is not None
        assert salt and '$' not in salt
        if not iterations:
            iterations = self.iterations
        hash = progressive_pbkdf2(
            password, salt, iterations, digest=self.digest,
            recommended_length=10, progression_factor=2)
        hash = base64.b64encode(hash).decode('ascii').strip()
        return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
6

You're trying to fix a human issue with a technical solution. And tbh, I fail to see how your increase in iterations will "incentivize users to use longer passwords" even with your suggestion that users would "feel" the difference in speed in longer vs. shorter passwords. Bumping iterations will not be noticeable to a user unless you set it to a ridiculous value.

So what kind of numbers are we actually talking about when you would implement iterations *= progression_factor**(recommended_length-len(password))? Tens of thousands? Hundreds of thousands? Millions? Billions? And how would you educate your users, that the experienced login slowdown is due to their shitty password instead of your perceived incompetence to code a performant site?

I mean, sure you can implement all your ideas but in the end, a weak password is a weak password - no matter how often you iterate the PBKDF. If your users are using 123456, correct horse battery staple, Spring2019!, 10inchcock, or similarly easy to guess/common passwords, all your endeavors will be in vain anyway.

My suggestion would be to educate your users to use stronger passwords/passphrases and incentivize them to adopt basic password hygiene, and not use common patterns like seasons, pet names, loved ones, DoB, etc.

  • The default iteration count used by the Django version I tested is 36000. And my performance measures showed 1000000 iterations per second on the hardware on which this would be used. So the user would be waiting at most 18 seconds with the numbers used in my question. Yes, obviously the users will have to be told about this practice. – kasperd Mar 2 at 10:08
  • 2
    @kasperd wait, so you enter your credentials and it goes - "Your password is too short, you'll have to wait for this"? Doesn't that give MORE information to an attacker? If an attacker knows Bob has to wait to log in, then the attacker will just need to try short passwords. Moreover, if you are doing the 10+ sec hashing for any password, including wrong ones, then if Bob types the first few letters of his long password and hits enter by accident, he'd need to wait a lot just to be able to write the correct one. Finally, that seems like a DOS attack vector - just input many short passwords. – VLAZ Mar 2 at 10:18
  • 1
    @kasperd then I suggest you go with this approach. It doesn't seem you are interested in being told "don't do it". – VLAZ Mar 2 at 10:46
  • 1
    The user is the weakest link here. If you think education will fail you might as well save yourself the hassle, grab a beer, and continue as usual. – SeeYouInDisneyland Mar 2 at 11:03
  • 5
    Don’t do it is the correct answer. You are allowing users with weak passwords to force you server to do a load more work. And giving a bad user experience. And an attacker watching the traffic can see that they have a weak password. Use a password strength meter and don’t let users register weak password. Use the Troy Hunt pwnd password system. Use known best practices don’t try to invent your own. – simbo1905 Mar 2 at 12:00
3

Your method isn't useful because you are only considering the length of the password, instead of considering the real factors that make passwords strong, which are entropy and possibly also the fact that they are not already present in a database of leaked or otherwise known passwords. So correcthorsebatterystaple or 1234567890 would probably be considered long enough by your system, but the truth is that they are likely to be cracked very easily if the attacker is using a dictionary or database of known passwords.

But even if we didn't consider what I just said, and we lived in an ideal world where every user only used passwords with random characters, the main problem is the following. Consider the following scenario, where the minimum accepted length is the minimum your system will accept, and the ideal password length is what you wish ever user would use in order to have a secure password. The passwords are random strings of characters, including symbols and everything (94 possible characters).

Minimum accepted lenght: 6 characters
Passwords to bruteforce: 94^6 = 6.9 x 10^11

Ideal password length: 12 characters
Passwords to bruteforce: 94^12 = 4.76 x 10^23

This means that, on average, cracking the ideal password is going to take about 10^12 times longer than the shortest accepted password. If you wanted the shortest accepted password (6 chars) to take as long as the ideal password (12 chars) to crack, then you would have to make every attempt for the short password last 10^12 times longer.

So if you set the ideal hashing time for the ideal password (12 chars) to 1 second, then you would have to set the hashing time for the minimum accepted password (6 chars) to 10^12 seconds, which is more than 30 thousand years. And I'm afraid users won't be willing to wait for thousands of years. In fact, users won't be willing to wait for more than a couple of seconds. So let's say each attempt is going to take at most 3 seconds, for the shortest accepted password. Then the ideal password is going to be hashed in 3 / 10^12 = 3 picoseconds. Again, this doesn't make sense.

As you can see, the problem is that between a weak password and a sufficiently strong password there is going to be an enormous difference in the number of possible combinations to try, but on the other hand there is only a relatively short range of times that you or your users are going to find acceptable.

  • It's true that I am obviously never going to make the short passwords as hard to brute force as the long ones. My own password for the system has 130 bits of entropy and there are users with passwords which has less than half of that. But why should that stop me trying to achieve some extra security for the short passwords? As indicated by my question my plan was to just double the iteration count for each character fewer than 10. That keeps the time to validate a password within a reasonable range. – kasperd Mar 2 at 14:32
  • 2
    @kasperd, I hadn't seen the exact parameters you put in the code. Anyway, by doubling the time for each char, it's like the attacker had to try twice on average, so it's the equivalent of 1 more bit of "virtual" entropy. That's not much, considering that a simple random lowercase letter has almost 5 bits. I'm also not sure how long a user will be waiting: a 6-char password will take 16 times longer to be hashed compared to a 10-char one, but how long is that? Making users wait for several annoying seconds just to gain a handful of "virtual" bits of entropy is not worth it, that's the point. – reed Mar 2 at 16:27
  • A factor 16 in the time to brute force a password does matter. If an increase of that magnitude didn't matter, I don't think the Django project would have increased the default iteration count from 12000 to 36000. Those users who are annoyed by the wait can avoid it by switching to a stronger password. That's one of the reasons I mentioned for considering this approach in the first place. – kasperd Mar 3 at 15:08
  • 1
    @kasperd, because probably the Django devs realized that 12000 rounds only took a fraction of a second on modern machines, so increasing that number was fine and maybe expected as good practice, although the resulting increase in security is almost insignificant. I'm not saying that your method will not make things more secure at all. I'm just saying that you are trading a very small increase in security for a potentially very weird UX (user experience). It's not worth it. – reed Mar 4 at 11:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.