The short answer
Quick answer: When you set up an authenticator app, the website generates a random secret key and shows it as a QR code. Your phone and the server now share that secret. Every 30 seconds, both sides independently run the same calculation: combine the secret with the current time, using a cryptographic function called HMAC, and reduce the result to six digits. Since both have the same secret and roughly the same clock, they arrive at the same code. No network connection is needed, and the code never has to be transmitted in advance. This standard is called TOTP.
What "two-factor" means
Authentication factors fall into three categories:
- Something you know: a password or PIN.
- Something you have: a phone, a security key.
- Something you are: a fingerprint or face.
Two-factor authentication (2FA) requires two from different categories. A password and a security question are both things you know, so that is not 2FA.
The point is simple. Passwords get phished, guessed and leaked in breaches, and a stolen login is a common way in for ransomware. If a login also requires a device the attacker does not have, a stolen password alone is not enough.
How TOTP works
TOTP (Time-based One-Time Password) is defined in RFC 6238, building on HOTP from RFC 4226.
Setting up
- The server generates a random secret, typically 160 bits.
- It displays the secret as a QR code encoding a link like this:
otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example&digits=6&period=30
- Your authenticator app scans the code and stores the secret.
- You enter one code to confirm that it worked.
From this moment, the server and your phone hold the same secret. It is never sent again.
Generating a code
Both sides perform the same steps:
- Take the current time as the number of seconds since 1 January 1970 (Unix time), and divide by 30, discarding the remainder. This gives a counter that goes up by one every 30 seconds.
- Compute an HMAC of that counter using the shared secret. HMAC is a keyed hash: without the secret, the output is unpredictable. The result is 20 bytes.
- Truncate. Use the last few bits of the result to choose a starting position, take 4 bytes from there, and treat them as a number.
- Take the last six digits of that number.
import hmac, hashlib, struct, time
def totp(secret: bytes, period=30, digits=6):
counter = int(time.time()) // period
digest = hmac.new(secret, struct.pack(">Q", counter), hashlib.sha1).digest()
offset = digest[-1] & 0x0F
number = struct.unpack(">I", digest[offset:offset + 4])[0] & 0x7FFFFFFF
return str(number % 10**digits).zfill(digits)
That is the entire algorithm. It works offline, because it needs only the secret and a clock.
Checking a code
The server runs the same calculation and compares. Because clocks are never perfectly aligned (see why clocks can't be trusted) and typing takes time, servers usually also accept the code from the 30-second window before and after.
A well-built server also:
- Limits attempts. Six digits is only a million possibilities, so guessing must be throttled.
- Rejects reuse of a code within its window.
HOTP, the counter-based ancestor
HOTP uses the same calculation with a counter in place of the time. The counter increases each time a code is generated, for example by pressing a button on a hardware token. The drawback is that the token and server can drift out of step. TOTP avoids this by using the clock as the counter.
Other second factors
SMS codes
The server generates a random code and texts it to you.
It is far better than a password alone, and it is also the weakest common method:
- SIM swapping. An attacker persuades the mobile operator to move your number to their SIM.
- Interception through weaknesses in phone network signalling.
- Phishing. A fake site asks for the code and uses it immediately.
- It depends on mobile coverage.
Push notifications
The service sends "Are you trying to sign in?" to an app on your phone. It is convenient, but attackers exploit prompt fatigue: triggering repeated requests until the user taps approve. Number matching, where you must type a number shown on the login screen, counters this.
Security keys and passkeys
These work on a different principle: public key cryptography, using the FIDO2 and WebAuthn standards.
- Registration. Your device creates a key pair for that website. The private key stays on the device. The site stores the public key.
- Login. The site sends a random challenge. Your device signs it with the private key, after you unlock it with a fingerprint, face or PIN. The site verifies the signature.
Two properties make this much stronger:
- Phishing resistance. The browser tells the device which site is asking, and the key only works for the site it was created for. A look-alike domain gets nothing useful, however convincing the page.
- No shared secret. The server holds only a public key. A breach of the server exposes nothing an attacker can log in with.
A passkey is this same technology used as the main sign-in method, often synchronised between your devices through a password manager or platform account. The FIDO Alliance describes how they work. A passkey combines possession of the device with a biometric or PIN, so it is multi-factor on its own, and it replaces the password entirely.
How the methods compare
| Method | Resists phishing | Works offline | Main weakness |
|---|---|---|---|
| SMS code | No | No | SIM swap, interception |
| TOTP authenticator app | No | Yes | Code can be relayed by a phishing site in real time |
| Push with number matching | Partly | No | Social engineering |
| Hardware security key | Yes | Yes | Cost; can be lost |
| Passkey | Yes | Yes | Depends on device or account recovery |
TOTP's limitation deserves a note. If you type your password and code into a convincing fake site, that site can pass both to the real one within the 30-second window. TOTP protects against leaked and reused passwords, not against real-time phishing.
Recovery
The weakest part of many 2FA set-ups is what happens when the device is lost.
- Backup codes. One-time codes issued at setup. Store them somewhere safe and offline.
- A second factor. Register two security keys, or a key plus an authenticator app.
- Authenticator backups. Some apps sync secrets to the cloud. This is convenient and moves the risk to that account's security.
- Account recovery flows. If recovery by email or support call is easy, an attacker will use that route instead. Recovery should be as strong as the login it bypasses.
For developers
- Use a well-tested library; do not implement the algorithm yourself in production.
- Store TOTP secrets encrypted. Unlike passwords, they cannot be hashed, because the server needs the original to compute codes.
- Rate limit verification attempts; see how rate limiters work.
- Offer passkeys or security keys, not only SMS.
- Require re-authentication to change or remove a second factor.
- Remember that a session token issued after login can still be stolen, so 2FA at login is not the whole story. See JWT vs sessions.
Frequently asked questions
How does an authenticator app work without internet?
It computes each code from a secret stored on the phone and the current time. Nothing needs to be received.
Why do 2FA codes change every 30 seconds?
The current time, in 30-second steps, is one of the inputs to the calculation. A new step produces a new code, so an intercepted code is only briefly useful.
Is SMS two-factor authentication safe?
It is much better than no second factor, but it is vulnerable to SIM swapping and phishing. Prefer an authenticator app, and better still a security key or passkey.
What is the difference between 2FA and a passkey?
2FA adds a second step to a password. A passkey replaces the password with a cryptographic key on your device, unlocked by a biometric or PIN, and it cannot be phished.
Conclusion
A six-digit code looks like magic and is just arithmetic: a shared secret, the current time, and a keyed hash. It stops attackers who only have your password. For protection against phishing as well, move to security keys and passkeys, which prove possession of a private key that never leaves your device.
Related articles
- How Passwords Should Be Stored (Hashing and Salting)
- How Public Key Cryptography Works
- How OAuth Lets You "Sign in With Google"
- Why Clocks Can't Be Trusted in Distributed Systems
