the six digits are just math

You log into some website. It asks for the six-digit code from your authenticator app. Even when you are off the internet, the code is still there. Thirty seconds later it changes. Somehow the website already knows the new one.

This feels like the phone and server must be secretly talking to each other. They are not. Your phone does not need the internet to receive the code because nobody is sending it a code. It’s just basic maths(insert normans meme)

The phone calculates it. The server calculates the same thing. If both started with the same secret and agree on roughly what time it is, they get the same six digits.

That is basically the entire magic trick.

Not all of 2fa works like this

2FA means proving who you are with two different kinds of evidence, or factors:

  • Something you know, like a password
  • Something you have, like your phone or a security key
  • Something you are, like a fingerprint

SMS codes, push notifications, hardware keys, recovery codes, and authenticator apps all work differently. The offline six-digit system we use via google auth or mircoslop is just TOTP, or time-based one-time password. A password plus a TOTP code is two-factor authentication because finding the password alone is not enough. The code by itself is not magically “two factor.” It is just the second proof.

The QR code is where it begins

When a website asks you to set up an authenticator, it shows a QR code. That square is not opening some live connection to the server. It mostly contains a long random secret and a little metadata about it.

Inside, it looks roughly like this:

otpauth://totp/example:mel?secret=JBSWY3DPEHPK3PXP&issuer=example

The important part is secret=.... The website generates a long random value there. Scanning the QR code copies it into your authenticator app. Now there are two copies, where the math is being done:

your authenticator app  ──> same secret <──  the website

The secret is normally written using Base32, which is just a convenient way to represent bytes. It is not encryption.

This is also why the setup QR code is easy to break. Anyone who photographs it can import the same secret and generate the same future codes. Basically like a second password for the time being.

Turning time into six digits

After setup, both sides runs the same maths to get to the six digit code.

First, take the current Unix time: the number of seconds since January 1, 1970. Then divide it into 30-second windows:

time_window = floor(unix_time / 30)

Every moment within the same window produces the same number. When the next window starts, the number increases by one. That is the little countdown circle in your authenticator app.

Next, combine this time window with the shared secret using a cryptographic function HMAC. Most authenticator setups use HMAC-SHA1, though the standard also allows SHA-256 and SHA-512:

digest = HMAC-SHA1(secret, time_window)

HMAC produces a long, unpredictable-looking result. The actual TOTP standard encodes the time window as eight bytes and then uses a step called dynamic truncation to select part of that result. Finally, it keeps the last six decimal digits:

code = truncated_result mod 1,000,000

The remainder is always between 000000 and 999999, which is exactly the six-digit shape we want. Leading zeroes count, so 004201 is a real code.

In more realistic way, it looks kinda like this:

counter = floor(current_unix_time / 30)
message = counter encoded as 8 bytes
digest  = HMAC-SHA1(secret, message)

offset  = last 4 bits of digest's final byte
number  = 4 bytes from digest starting at offset
number  = number with its sign bit removed

code    = number mod 1,000,000

The truncation details look cursed because cryptographic standards are supposed to be complicated for some reason, but the model is simple:

shared secret + current 30-second window = current code

Same secret, same math, same output.

Why no internet is required

Your phone already has both inputs it needs:

  1. The secret it received when you scanned the QR code
  2. The current time from its own clock

The website has its copy of the secret and it does its own math. Neither side needs to send the code to the other beforehand. They calculate it independently and only compare results when you log in.

This is similar to two people agreeing on a secret formula and starting their stopwatches together. They can go to opposite sides of the planet and still write down the same answer every thirty seconds.

The authenticator works w/out internet because time still passes. It might even work on another planet, Big if True.

What if the clocks are slightly wrong

Phones and servers are not perfectly synchronized. Your code might arrive near the exact moment one 30-second window becomes the next. If the server checked only its current window, correct codes would randomly fail around that boundary, But they do not work this way, Servers usually check a small range: the current window, and perhaps one window before and after it.

phone says window 1000
server checks 999, 1000, and 1001

This thingy handles a little clock drift and network delay. Too much tolerance would keep codes valid for longer and make guessing easier, so the window stays small. If your phone’s clock is badly wrong, your codes stop working even though the secret is correct.

Why six digits are enoughish

A six-digit code has only one million possibilities. That sounds large until you remember that, computationally, counting to one million is basically nothing. The reason TOTP survives that tiny search space is that you are not supposed to get a million guesses.

The shared secret is the strong part. A good secret has far more possible values than the displayed code, and HMAC makes it impractical to work backward from observed codes to recover that secret. Seeing 381204 now does not let you calculate the next code.

The short lifetime also helps. Stealing one code normally gives an attacker only seconds to use it. But “normally” is doing some work there.

HOTP, the version without a clock

TOTP comes from an older design of HMAC-based one-time password. Instead of the current time window, HOTP uses a counter that increases after every code:

shared secret + counter = code

TOTP basically replaces “how many codes have we generated?” with “which 30-second window are we in?”. That makes synchronization easier because both devices already have clocks.

tldr;

Your authenticator app is not receiving six-digit messages from every service on the screen. It is storing a different shared secret for each account and repeatedly running the same standardised calculation.

When you scan the QR code, the service gives your phone a secret. Every thirty seconds, both sides combine that secret with the current time window using HMAC and shorten the result to six digits.

Nothing is being sent to your phone. There is no daemon whispering codes into it in the background. Both sides simply know the same secret, look at roughly the same clock, and arrive at the same answer.

Very Unix, really: a small secret, a small function, and an unreasonable amount of faith hoping the clock is in sync. If you want the exact recipes, they live in RFC 6238 for TOTP and RFC 4226 for HOTP.

cool people & cool stuff