Table of Contents
The need for 2FA
Passwords alone no longer seem enough. Especially, if you use the same password across multiple sites, a breach at one service exposes all your accounts. Attackers use database leaks, phishing sites, and credential stuffing attacks to steal and exploit passwords. Even the strongest passwords can be neutralized if they are leaked or keylogged. To this end, two-factor authentication (2FA) adds a very crucial second requirement: “something you have” in addition to “something you know” (passwords). This second factor proves you possess a physical token, making stolen passwords useless without that token.
Many people start with SMS codes as a convenient form of 2FA, but these can have serious weaknesses. SIM swap attacks may let criminals redirect your phone number to their device. SS7 protocol flaws could let attackers intercept SMS messages. These codes also require cellular service and can be delayed in cases of low signal.
Hardware security keys such as YubiKey offer much stronger protection. Instead of generating temporary codes, they use cryptographic proof tied directly to the website you’re logging into. This makes them highly resistant to phishing attacks and code interception. However, they require carrying a separate physical device, may cost money, and are generally not a feasible solution for regular users or multi-device setups.
Authenticator apps surface as the ideal solution in the middle. They’re resistant to any interception or swap attacks because nothing is transmitted during code generation or verification. They are conviniently located in your phone (linked to your email), and most importantly, do not rely on an internet connection to generate codes. (Yes! If you didn’t know this, you can try it by putting your phone on flight mode and trying to use the 2FA.
The Core Concept: Simultaneous Calculation
Authenticator apps solve a clever problem: two devices (the application server and your mobile) that never talk to each other must somehow agree on a code. They do this by independently computing the same answer using shared information.
Three ingredients make this work:
- A shared secret key - A random string both devices share during initial setup
- The current time - Both devices agree on what time it is
- A cryptographic algorithm - Both devices run the same mathematical process
Think of it like two people cooking the same recipe. They have the same ingredients (shared secret), the same recipe (algorithm), and they check their watches to start at the same time (time synchronization). Even though they’re in different kitchens, they’ll produce identical dishes.
This “simultaneous calculation” means your phone and the authentication server independently arrive at the same 6-digit code without ever communicating during the login process.
Setting up 2FA
The first time you set up an authenticator app, you scan a QR code (or type in a long secret code). This is how the shared secret gets distributed. Let’s walk through what actually happens:
Server Side: The authentication server generates a random secret key. This is usually a base32-encoded string like JBSWY3DPEHPK3PXP. The server stores this secret linked to your account.
QR Code Contents: The QR code contains a special URI format that looks like:
1otpauth://totp/GitHub:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&algorithm=SHA1&digits=6&period=30
This URI includes:
totp- indicates time-based codes- The account name and issuer
- The secret key encoded in base32
- Configuration options like algorithm (SHA1) and code length (6 digits)
- The time period (30 seconds)
Phone Side: Your authenticator app scans this code and extracts the secret. It stores this value locally, usually in an encrypted database, alongside the rest of the metadata. Your phone now has everything needed to generate codes without ever contacting the server again.
At this point, the secret exists in exactly two places: your phone and the authentication server. This shared secret is what makes the whole system work.
Deterministic Algorithms
This is where the “magic” happens. Both your phone and the server take the secret key, perform identical calculations on it and produce the same code. Let us break down each step :
The Time Window
Both devices work with Unix timestamps - the number of seconds since January 1, 1970. But instead of using the exact time, they divide it into fixed windows.
For 30-second codes, the calculation is:
1time_step = floor(current_unix_time / 30)If the current Unix time is 1,716,400,000, dividing by 30 gives us 57,213,333. This number represents the current 30-second window. Both devices compute the same time step because they’re looking at roughly the same time (Modern devices are synced to ms precision).
During that 30-second window, the time_step value stays constant, so the generated code remains the same. When the window flips to the next 30 seconds, a new code is computed.
The Shared Secret
The secret key never changes after setup. It acts as a cryptographic seed - a way for two parties to derive the same sequence of codes without storing them.
If someone gets a copy of your secret, they can generate identical codes on their device. This is why protecting the secret matters more than protecting the physical phone. Losing your phone doesn’t compromise the codes if the secret stays safe.
HMAC: The Cryptographic Engine
Both devices run an HMAC operation using SHA-1 (Hash Message Authentication Code). This combines the shared secret with the current time step:
1HMAC-SHA1(secret, time_step)HMAC produces a deterministic output - the same inputs always generate the same hash. More importantly, even tiny changes to the inputs create completely different outputs. A different secret produces an unrelated hash, ensuring attackers can’t guess codes without the secret.
Although, SHA-1 is considered weak for digital signatures, but HMAC-SHA1 remains secure for this use case. The attacks against SHA-1 don’t meaningfully help an attacker trying to forge authenticator codes.
Dynamic Truncation
The HMAC output is a 20-byte (160-bit) hash. We need to convert this to a 6-digit number. This is done through dynamic truncation:
- Take the last 4 bits of the hash as an offset (0-15)
- Use that offset to grab 4 bytes starting at that position
- Treat those 4 bytes as a 32-bit unsigned integer
- Apply modulo 10^6 to get a 6-digit number
This process always produces a number between 0 and 999,999. If the result has fewer than 6 digits, we pad it with leading zeros.
Why Codes Change Every 30 Seconds
The code changes because the time step input changes. Every 30 seconds, the time step increments by 1:
| Time Window | Time Step | Generated OTP |
|---|---|---|
| 10:00:00–10:00:29 | 57,213,333 | 482991 |
| 10:00:30–10:00:59 | 57,213,334 | 104337 |
Both devices independently compute these different codes because they’re using different time steps as input. No server message or push notification tells them to change - it happens automatically through just checking the latest time.
How Servers Verify Your Code
When you enter a code, the server has to verify it matches what it expected:
- Retrieve the secret - Look up your account’s stored secret key
- Compute expected code - Run the same TOTP algorithm using current time
- Compare values - Check if your entered code matches
- Apply tolerance - Check adjacent time windows for clock drift
Servers typically check not just the current 30-second window, but also the previous and next windows. This gives a 90-second acceptance window to account for slight time differences between your phone and the server.
Most systems also allow a few seconds of leeway because phone clocks can drift slightly from the true time. However, significant clock drift will cause codes to fail, which is why keeping your phone’s time accurate is important.
HOTP vs TOTP: The Underlying Standards
Authenticator apps implement TOTP (Time-based One-Time Password), defined in RFC 6238. This standard builds on HOTP (HMAC-based One-Time Password) from RFC 4226.
HOTP uses a counter instead of time:
1HOTP(secret, counter)Each time a code is generated, the counter increments. This creates a sequence that diverges if the two devices get out of sync. TOTP solves this by using time as the universal counter, ensuring both devices automatically stay in sync as long as their clocks agree.
TOTP essentially becomes HOTP with a time-derived counter value:
1TOTP(secret) = HOTP(secret, floor(current_time / 30))This elegant relationship means TOTP is just a specific application of the HOTP standard.
Security Analysis of Authenticator Apps
The TOTP system has several security advantages:
Shared secrets stay local: The secret never leaves your device during normal operation. Even when logging in, only the generated code is transmitted, not the secret itself.
Codes expire quickly: A 6-digit code is only valid for 30 seconds. An attacker who somehow intercepted a code would have very limited time to use it.
Resistant to replay attacks: Since codes change constantly, intercepting a code doesn’t help an attacker. The code will be different by the time they try to use it.
Offline generation: Codes are generated locally without network requests, eliminating network-based attacks during code generation.
Brute force protection: With 1 million possible 6-digit codes, guessing the right one is unlikely. Rate limiting on login attempts makes automated guessing impractical.
Weaknesses and Real-World Attacks
No system is perfectly secure. TOTP has some important limitations:
Phishing: While TOTP prevents replay attacks, real-time phishing can still work. Sophisticated phishing attacks can nullify any authenticator security if executed well.
Malware on rooted devices: If your phone is compromised, malware can extract the secret keys from your authenticator app, effectively cloning your codes.
Cloud backup risks: Some authenticator apps now offer cloud backup of secrets. While convenient for device transfers, this creates a new attack surface where compromising your cloud account could expose all your TOTP secrets.
No protection against coercion: Someone can force you to unlock your phone and generate codes. Hardware security keys may provide some protection against this.
Despite these weaknesses, TOTP remains significantly more secure than SMS-based 2FA, especially against the common threat of SIM swap attacks.
Beyond OTPs: The Move Toward Passkeys
While TOTP revolutionized 2FA, newer technologies are emerging. Passkeys, built on WebAuthn and FIDO2 standards, use public-key cryptography instead of shared secrets.
Passkeys offer several advantages over TOTP:
- They’re resistant to phishing by design
- No manual code entry required
- Based on cryptographic key pairs rather than shared secrets
- Built-in protection against replay and man-in-the-middle attacks
However, passkeys are still relatively new and not universally supported. TOTP remains the most widely compatible and definitely the most bang-for-buck security control you can add to your digital systems.
This elegant solution demonstrates how cryptography can solve distributed agreement problems without requiring constant communication. The code on your screen is not sent to you - it is rediscovered simultaneously by two machines that already share the same cryptographic truth.
