Time-based One-time Passwords
I recently had to switch phones, because my old phone conked out. I had an app on that phone that I used for short-lived MFA codes for various logins I use. That app was a poor choice, because it didn’t allow for a backup of the secrets used for the code generation, so I had to go to the relevant logins and one by one remove MFA, then add it again. While doing so, I was wondering how this stuff works underneath, so I started looking into this.
Soon enough I found the standard defining the logic: RFC 6238 defines how a time-based one-time password can be generated for a time window. The general idea is simple and straight forward:
- Use an arbitrary point in time that serves as
T0
. By default, the Unix epoch is used. - Calculate the delta of the current time
T
toT0
, in seconds. By default, this is the Unix time. - Take this time delta and use integer division by the desired time window size. By default, the time window is 30 seconds.
- Feed the resulting integer value into an HMAC (using HMAC-SHA-1 by default) that is keyed with the shared secret between the service requiring MFA and the user.
- Run some arithmetic on the resulting hash to deterministically extract an
N
-digit code. By default,N = 6
is used.
If you do have an app on your device that generates new codes every so many seconds, chances are that it works exactly like this. So as the user, you pass the current code to the server who can run the same calculation to see if you know the same shared secret. The server may want/need to compare the values for the time window coming before (and/or) after the current one, to account for clock skew between your device and the server, but that still means at most 2-3 codes need to be compared per attempt.
At the same time, with a 6-digit code, an attacker who doesn’t know the shared
secret would have to try up to O(10^6)
combinations, something which is kind
of easy for a server to detect and block.
Some Code
I ended up writing a bit of TypeScript code to demo the functionality using a hard-coded “secret”. You can find it on GitHub. The repo also has QR codes that you can scan with your favorite TOTP-generating MFA app to verify the output.
There is no code in there to generate cryptographically secure random secrets, or to generate QR codes for registering with MFA apps, but it does have some pointers that should be useful enough to do both in just a few minutes.