A 2FA code from an authenticator app is a number worked out from two things: a secret key that you and the website both hold, and the time. Below, your browser works one out from its own clock, one step at a time, so you can see every part of it.
Never paste a real setup key into any website, including this one.
This page uses one public demo key, JBSWY3DPEHPK3PXP. It belongs to no account, and there is nowhere on this page to type a key of your own.
Everything runs on your device. The page makes no requests after it loads, stores nothing and has no analytics.
Live
Watch it live
The steps below update with your device’s clock. Each has a plain-language note beside it. Use Pause to hold the numbers still while you read.
This browser can’t do the arithmetic this page needs, so the worked example below is shown instead.
1. The key
The secret key is a short string of letters and digits, written in base 32. Your phone and the website hold the same one. This one, JBSWY3DPEHPK3PXP, decodes to these 10 bytes.
48656c6c6f21deadbeef
2. The time
Your device reads its clock. The number of seconds since midnight UTC on 1 January 1970 is divided by 30 and rounded down. RFC 6238 writes it as “T = (Current Unix time - T0) / X, where the default floor function is used in the computation.” That whole number is the time-step counter. It stays the same for 30 seconds, then goes up by one.
Time on your device
Seconds since 1 January 1970
Time-step counter
Seconds left before the code changes: seconds left
3. The scramble
The counter, written as 8 bytes, and the key go into HMAC-SHA-1, a standard way to mix a key and a message into 20 bytes. RFC 4226 writes it as “HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))”. A different counter gives a completely different result.
The counter as 8 bytes
HMAC-SHA-1 result, 20 bytes in hexadecimal
4. Picking four bytes
Twenty bytes is too long to type. RFC 4226 says: “Let OffsetBits be the low-order 4 bits of String[19]”. So the last digit of the last byte, outlined below, says where to start. The four bytes from there, shown underlined, are used, and their top bit is cleared.
Where to start (the last digit of the last byte)
The number those four bytes make, with the top bit cleared
5. The six digits
The number is divided by 1,000,000 and the remainder is kept. RFC 4226 writes it as “Return D = Snum mod 10^Digit”. Leading zeros are kept. A website that holds the same key and has the same clock does the same sum and gets the same six digits.
Current 6-digit code
Pause only freezes this page. Your authenticator app keeps going, so after Resume the codes match again.
To check it against a real authenticator app, scan this QR code with the app. The code is drawn on this page, on your device. It holds the address below, which carries the demo key and the name “MELKEM demo”. If your phone’s clock and this device’s clock agree, the app shows the same six digits, changing at the same moment. Delete the demo entry from your app afterwards: it protects nothing.
This is the same sum done for one moment: 22:13:20 UTC on 14 November 2023, which is 1700000000 seconds after 1 January 1970. The live version above needs JavaScript.
The time-step counter is 1700000000 divided by 30, rounded down: 56666666. As 8 bytes in hexadecimal, that is 00 00 00 00 03 60 aa 2a.
HMAC-SHA-1 of those 8 bytes, with the demo key, gives these 20 bytes: 1d 70 6e 94 1a c7 6b 6d 4a 46 dd 6f af a4 5f e3 35 11 bf 86.
The last byte is 86, so the last digit is 6. The four bytes starting at position 6 are 6b 6d 4a 46.
Those four bytes, with the top bit cleared, make the number 1802324550.
1802324550 divided by 1,000,000 leaves 324550. The code is 324 550.
The address a QR code would hold for this demo key is otpauth://totp/MELKEM%20demo:demo?secret=JBSWY3DPEHPK3PXP&issuer=MELKEM%20demo
Offline
Why codes work without a connection
Your phone doesn’t ask anyone for the code. It already has the key, and it has a clock. RFC 6238 puts the shared part like this: “The prover and verifier MUST either share the same secret or the knowledge of a secret transformation to generate a shared secret.” The website does the same sum on its side and compares the answers. That is why a code still appears in airplane mode.
Clock
Why the phone’s clock matters
The counter comes from the time, so a clock that is wrong makes codes the website does not expect. RFC 6238 says: “The precision of the time used by the prover affects how often the clock synchronization should be done”. It also recommends a limit on how much delay a website allows: “We RECOMMEND that at most one time step is allowed as the network delay.”
If a code is refused, check that your phone sets its date and time automatically.
Expiry
Why codes expire
The counter goes up once every 30 seconds, so the code changes with it. RFC 6238 says: “We RECOMMEND a default time-step size of 30 seconds.” It gives the reason: “This default value of 30 seconds is selected as a balance between security and usability.”
A short life limits what a stolen code can do, but 30 seconds is enough for a scammer to use a code you read out. The RFC notes: “When an OTP is generated and exposed to a third party before it is consumed, the third party can consume the OTP within the time-step window.” That is why you should never share a code. See why you should never share your 6-digit code.
The arithmetic on this page is checked against the test values in those two documents: the HOTP values in Appendix D of RFC 4226, and the SHA-1 values in Appendix B of RFC 6238.