2

I am building a website and I am implementing two factor authentication (2FA). The second factor will be a random 6 digit code like those from Authy. This site will store very confidential information.

I am considering limiting the amount of tries a user gets to fill in their 2FA code before locking them out. This to prevent brute forcing. But what amount of tries is reasonable? Or should there be a temporary lockout, since 2FA codes rotate and the attacker will not gain anything from it?

  • 2
    I'd just like to restate AviD's "security at the expense of usability comes at the expense of security" rule. Most modern implementations of 2FA are built in a way that makes bruteforcing very difficult. (Google Authenticator, for example). 2FA authentication systems usually provide fast-expiring keys and I, as an end user who is on the low end of tech savviness, could be greatly frustrated if I am too slow to enter my 2FA 5-10 times in a row and end up getting locked out of my account. – Ivan T. May 16 '18 at 13:49
  • 2
    Another way of preventing brute-force is to have a time delay between logon attempts. If you lock users out after a number of bad attempts, then an attacker can make bad attempts deliberately to lock them out. If you're going with locking users out, you may want to lock them out for a given time period. – S.L. Barth May 16 '18 at 13:51
  • 1
    @S.L.Barth Thanks, that would also be an option. Just a timeout after too many wrong tries. – Eelke May 16 '18 at 14:03
  • 1
    @Eelke have you considered instead putting in a rate limit on the code? Perhaps 5 seconds or so between attempts? If someone can only try 6 of 1 million possible passwords before the code rotates, they're probably going to be there for a very, very long time before they luck out. Like, heat death of the universe long. – Adonalsium May 16 '18 at 14:31
10

Many people falsely assuming that because the code rotates often, it is safe from brute force. This is not the case. If we assume 100 attempts per second and a code that is valid for 30 seconds, an attacker has 100*30 = 3000 guesses before the code rotates, this has a 3000 / 10^6 = 0.3% chance of success. This may seem low but this is only for 30 seconds, an attacker can try again for subsequent codes. For a 50% chance of success, they need only try for ln(50%) / ln(99.7%) = 230.7 30 second periods, or about 2 hours.

If you're not sure why rotating codes doesn't solve this, try thinking of it this way: an attacker can guess the range 000000 to 002999 for each code, and a code has a 3000 / 1000000 = 0.3% chance of being in that range, so after generating many codes there is a good chance that one will eventually fall in that range.

If you rate limit attempts to 1 every 5 seconds, it becomes ln(50%) / ln(99.9994%) 30 second periods, about 40 days. This still may not be good enough for a high value account, so I would definitely recommend additional measures to prevent brute force. What these measures should be though is harder to answer, as stated in the comments completely locking an account due to failed second factor attempts may allow DoS, and in general could be frustrating for users. It may be sufficient to log each attempt and warn the user to change their password if it passes a certain number of attempts per minute for the past day or so.

The Math

Let s be the chance of success for a single 30 second period
Let n be the number of 30 second periods
Let t be the total chance of success over all periods

If you have s chance of success, then you have 1 - s chance of failure. If you try multiple times, you can multiply the chance of failure for each try to get the total chance of failure, so (1 - s)^3 is the chance of failing with 3 tries, and 1 - (1 - s)^3 is the chance of succeeding in at least 1 of 3 tries. This allows us to come up with the following equation:

t = 1 - (1 - s)^n

Then we can solve for n given s and t:

(1 - s)^n = 1 - t
ln( (1 - s)^n ) = ln(1 - t)
n * ln(1 - s) = ln(1 - t)
n = ln(1 - t) / ln(1 - s)

So for t = 0.5 and s = 0.003:

n = ln(1 - 0.5) / ln(1 - 0.003)
  = ln(0.5) / ln(0.997)
  ≈ 230.7

If you want to know how long it would take an attacker to have a 99% chance of success with 6 tries per period (1 every 5 seconds), s = 6/1000000 = 0.000006 and t = 0.99, giving about 767,526 periods or 266.5 days. For a 1% chance at the same rate t = 0.01, which gives about 1675 periods or 14 hours.

  • 1
    Thanks, this helps calculating the risk and making an informed decision. – Eelke May 17 '18 at 11:21

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.