2

According to the OWASP guidelines:

Authentication failure responses should not indicate which part of the authentication data was incorrect. For example, instead of "Invalid username" or "Invalid password", just use "Invalid username and/or password" for both. Error responses must be truly identical in both display and source code.

Let's say a naive implementation of this (in pseudo code) is as follows:

function authenticate(username, password):
    let hashed_password = get_user_from_model(username)

    if user is not found:
        return "Invalid username and/or password"

    if verify_bcrypt(password, hashed_password):
        return "Authenticated"
    else:
        return "Invalid username and/or password"

Assuming that the get_user_from_model function takes a neglible amount of time and the verify_bcrypt takes 400ms, an attacker may be able to deduce whether the function returned when the password was invalid or username was invalid using a timing attack. What would be the best practice in order to stop this? Is something like sleeping for a constant amount of time a good idea? How would this work across different systems that might take different amount of time to verify the hash?

3

The easiest option I can see here is to avoid immediately returning from the first branch of the code. Instead, continue forward with a bogus hashed_password value and still do the verification.

function authenticate(username, password):
    let user = get_user_from_model(username)
    let hashed_password = BCRYPT_HASH_FOR_MISSING_USERS

    if user is found:
        hashed_password = get_password(user)

    if verify_bcrypt(password, hashed_password) and user is found:
        return "Authenticated"
    else:
        return "Invalid username and/or password"

This way you always do the same steps - you would do a roundtrip to the storage to fetch the user information and do the hashing as well. Only the verification against a non-existent user would always fail.

  • Would that not waste compute cycles? Can an attacker not then go ahead and spam you with bad usernames? – MoroseBurrito Mar 2 at 20:13
  • 1
    Well, if you want to be as efficient as possible, then I suppose it does waste CPU cycles. But I doubt it matters that much - you have to try and fetch the user at any point anyway. If you are worried about the hashing algorithm taking too long, then that's already a problem if an attacker simply uses legitimate usernames which will go through the same steps as this solution. – VLAZ Mar 2 at 20:17
  • @MoroseBurrito: you are using those compute cycles to occlude the username (non-)existence. It would be more efficient to not compute the hash, but that would leak that the username exists. (Not that the cpu cost would that much different, and as VLAZ mentioned, you would have the same issue with known-to-exist usernames, if that's an issue you would need throttles/capchas anyway) – Ángel Mar 2 at 23:30
  • @Ángel technically, you could save on processing if you can cause the application to stop and wait for the exact time that a hash would be computed. Waiting is usually a lot less expensive. Still, you'd have to keep accounting for the hardware you run this on. Which you could by measuring the hashing and adjusting the average wait time accordingly. But going down that route, is a rabbit hole that doesn't actually save you as many CPU cycles as you have to constantly calculate and adjust to save any. And it's a lot more complex to maintain. – VLAZ Mar 3 at 9:06

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.