How Multi-Factor Authentication Works: From Passwords to Time-Based One-Time Passwords

December 22, 2026 · 19 min read

Part of Under the Hood — deep dives into the technology we use every day.

Before there were passwords, there were doors.

Actual doors. With actual humans standing in front of them. A guard at a gate who looked at your face, recognised you, and let you through. A shopkeeper who knew your voice. A banker who checked your signature against the one on file. Authentication – proving you are who you say you are – has always been about the same thing: giving someone enough evidence that they’re willing to trust you.

For most of human history, that evidence was physical. You showed up in person. Someone who knew you vouched for you. You carried a letter sealed with wax, and the shape pressed into the wax told the recipient the letter was genuine. The seal was the authentication. Break it and you broke the trust.

Then we moved our lives onto computers, and computers can’t recognise faces (well, they can now – but that’s getting ahead of things). They can’t read wax seals. They needed something simpler. Something they could check mechanically.

They needed passwords.

One factor – the thing you know

Here’s the simplest version. You have an email account. To get in, you type a password. The server checks it against the password it has on file (or, if it’s been built properly, against a cryptographic hash of that password). If they match, you’re in.

This is single-factor authentication. One piece of evidence. One factor.

It’s like a combination lock on a garden gate. Anyone who knows the combination can open it. It doesn’t matter who they are, what they look like, or whether they should be there. The lock doesn’t care about identity – it cares about knowledge.

Now, the formal language. The information security community divides authentication factors into three categories:

  • Something you know – a password, a PIN, the answer to a security question.
  • Something you have – a physical device like a phone, a security key, a bank card.
  • Something you are – a biometric: your fingerprint, your face, your iris pattern.

A password is “something you know.” It lives in your head (or, more realistically, in your password manager). It’s intangible. Copyable. Shareable. And that’s where the trouble starts.

Why one factor isn’t enough

Passwords get stolen. They get stolen in so many ways that security researchers have entire taxonomies for the methods. Here are the big ones.

Credential stuffing. You used the same password for your email and for that forum you signed up to in 2014. The forum got breached. Its database – including your email address and password – ended up in a dump that’s now available to anyone who looks. An attacker takes that email-password pair and tries it against Gmail, Outlook, your bank, your work VPN. If you reused the password, they’re in. This isn’t theoretical. Troy Hunt’s Have I Been Pwned service has indexed over 13 billion compromised accounts from more than 700 data breaches as of 2024.

Phishing. You get an email that looks like it’s from your bank. The link takes you to a page that looks like your bank’s login page. You type your username and password. Except the page isn’t your bank – it’s an attacker’s server, and they’ve just recorded your credentials. Phishing has been the dominant attack vector for credential theft for over two decades.

Brute force and dictionary attacks. If your password is “password123” or “Summer2026!” – and a startling number of passwords look like that – an attacker can simply try common passwords until one works. Modern hardware can test billions of password hashes per second.

Shoulder surfing, keyloggers, social engineering. Someone watches you type. Malware on your machine records your keystrokes. Someone calls you pretending to be from IT and asks for your password. The human layer is often the weakest.

The core problem is simple: a password is a single piece of information, and once it’s compromised – by any method – the attacker has everything they need. There’s no second check. No fallback. No “but do you also have…”

Two factors – the thing you know and the thing you have

Here’s a system that does have a second check, and it’s older than computers.

Your bank card at an ATM.

You insert the card. The machine asks for your PIN. You type it. The machine checks both: does this card exist and is it valid (something you have), and does this PIN match the one on file for this card (something you know)?

Neither factor alone is enough. If someone steals your card, they still need the PIN. If someone sees you type your PIN, they still need the physical card. An attacker has to compromise both factors – from different categories – to get in.

This is multi-factor authentication. MFA. Sometimes called two-factor authentication (2FA) when exactly two factors are used.

The key word is different. Two passwords is not MFA. A password plus a security question is not MFA – both are “something you know.” The factors must come from distinct categories. This isn’t pedantry; it’s the whole point. Different categories have different attack surfaces. Stealing knowledge (a password) requires a different kind of attack than stealing a physical object (a phone), which requires a different kind of attack than forging a biometric (a fingerprint). Requiring two factors from different categories forces the attacker to mount two fundamentally different attacks, and that’s exponentially harder than mounting one.

The problem with “something you have” on the internet

Right. So MFA works brilliantly at an ATM. You insert a physical card into a physical machine. The machine reads the chip. The physicality of it is the point.

But you can’t insert a bank card into a website.

When you’re authenticating over the internet, you need a way to prove you possess a specific device, remotely, right now, without physically presenting it. You need a “something you have” factor that works over a wire.

This is the problem that Time-Based One-Time Passwords – TOTP – were designed to solve.

How TOTP works – building up from first principles

Let’s build it up, layer by layer.

The shared secret. When you set up an authenticator app – Google Authenticator, Authy, 1Password, whatever – the service shows you a QR code. You scan it with your phone. What just happened?

That QR code contains a secret key. A random string of bytes – typically 160 bits, encoded in Base32 for human-readability – that was generated by the server at the moment you enabled MFA (as specified in RFC 4226). When you scanned the code, the key was transferred to your phone. Now both your phone and the server know this secret. Nobody else does. It was shown once, on your screen, and it should never be transmitted again.

This is the “something you have” factor. Not your phone itself – the secret stored on your phone. Your phone is the container; the secret is the credential.

The time step. TOTP divides time into windows. By default, each window is 30 seconds long. To figure out which window we’re currently in, we take the current Unix timestamp (the number of seconds since midnight UTC on 1 January 1970) and divide by 30, rounding down:

T = floor(unix_time / 30)

Right now, as I write this, the Unix timestamp is somewhere around 1,780,000,000. Divide by 30, floor it, and you get a counter value – a number that ticks up by one every 30 seconds, synchronised to UTC. Everyone in the world who does this calculation at the same moment gets the same number.

The HMAC. Now we combine the secret and the time. TOTP uses HMAC-SHA-1 – a keyed hash function that takes a secret key and a message, and produces a 20-byte (160-bit) digest. The key is the shared secret. The message is the time step counter T (encoded as an 8-byte big-endian integer):

hash = HMAC-SHA-1(shared_secret, T)

HMAC is a specific construction for turning a hash function into a message authentication code. The crucial property is that without the secret key, you can’t produce the correct hash – and you can’t work backwards from the hash to discover the key. The output looks random to anyone who doesn’t know the secret.

The truncation. We now have 20 bytes of hash. But a TOTP code is 6 digits. How do we get from one to the other?

This is where it gets clever. RFC 4226 defines a process called dynamic truncation:

  1. Take the last 4 bits of the 20-byte hash. These give you a number between 0 and 15. Call it the offset.
  2. Starting at byte position offset, extract 4 bytes from the hash.
  3. Mask off the most significant bit (the sign bit) to ensure you get a positive 31-bit integer.
  4. Take the result modulo 1,000,000. That’s your 6-digit code.

Why use the last nibble as an offset instead of just taking the first 4 bytes? Because it makes the extraction point unpredictable to an attacker who might try to bias the output. The offset is itself derived from the hash, creating a kind of internal randomisation.

Here’s the whole thing as pseudocode:

T = floor(unix_time / 30)
hash = HMAC-SHA-1(shared_secret, T)
offset = hash[19] & 0x0F
code = (hash[offset:offset+4] & 0x7FFFFFFF) % 1_000_000

Six lines of logic. That’s all there is. The elegance of TOTP is that it reduces “prove you have this device” to “prove you know a number that only this device could have calculated, and that changes every 30 seconds.”

The verification. When you type the 6-digit code into the login page, the server performs the exact same calculation – same shared secret, same time step – and checks whether the codes match. Because clocks aren’t perfect, most servers accept codes from the current time step and the one immediately before and after it (a window of plus or minus one step), giving you roughly 90 seconds of validity rather than exactly 30.

The QR code, decoded. That QR code you scanned earlier? It encoded a URI that looks like this:

otpauth://totp/Example:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30

The otpauth:// scheme tells the authenticator app what kind of credential this is. The secret parameter is the Base32-encoded shared key. The issuer is the service name. The algorithm, digits, and period parameters are usually SHA1, 6, and 30 – the defaults from the RFC – but they can be overridden (the Key URI format is documented openly). It’s an open format. Any authenticator app can read it. That interoperability is one of TOTP’s great strengths.

Entropy – randomness as a security property

Let’s talk about how hard these things are to guess.

Start concrete. A 4-digit PIN – the kind you type at an ATM – has 10,000 possible values (0000 through 9999). If you’re guessing at random, you have a 1-in-10,000 chance each try. Not bad for a determined attacker, which is why ATMs lock you out after three wrong attempts.

A 6-digit TOTP code has 1,000,000 possible values. A 1-in-a-million chance per guess. In information theory, we measure unpredictability in bits of entropy – the base-2 logarithm of the number of possible values. A million possibilities is about 19.9 bits of entropy (because log2(1,000,000) is approximately 19.93, following Shannon’s information theory).

That doesn’t sound like much. And on its own, it isn’t. If an attacker could try a million codes in 30 seconds, they’d find it. But they can’t – servers rate-limit attempts, and the code expires. The code’s entropy is low, but its lifetime is short, and that combination is what makes it work.

The real security isn’t in the code. It’s in the shared secret behind it. A 160-bit shared secret has 2160 possible values – that’s roughly 1.46 x 1048. Even at a billion guesses per second, it would take longer than the age of the universe to try them all. The code is a window into the secret; the secret is the fortress.

How TOTP increases authentication entropy. A password alone might carry 30 to 50 bits of entropy, depending on its length and complexity (Bonneau’s empirical analysis of 70 million passwords found typical guessing entropy of 20–40 bits). Adding a TOTP code doesn’t simply add 20 bits to that number. It adds a different dimension of unpredictability – one that changes every 30 seconds, and that requires compromise of a separate device. An attacker who steals your password still needs to also steal (or intercept) the TOTP code within its validity window. The attack surface is fundamentally wider.

A note on key generation. RFC 4226 specifies that the shared secret must come from a cryptographically secure random number generator – not a pseudo-random one, and certainly not something derived from your username or email address (RFC 4226). The RFC recommends a minimum of 128 bits, with 160 bits preferred. Some implementations have historically fallen short – early versions of Google Authenticator used 80-bit secrets – which reduces the security margin. The secret is the foundation of everything. If it’s predictable, nothing else matters.

Attacking these controls

TOTP is good. It’s a genuine, significant improvement over passwords alone. But it’s not invincible. Understanding where it fails is just as important as understanding how it works.

Adversary-in-the-Middle phishing (AitM). This is the big one.

The attacker sets up a website that looks identical to the real login page – your bank, your email provider, your company’s VPN. You arrive at the fake site (via a phishing email, a malicious search result, a typosquatted domain). You type your username and password. The fake site relays them to the real server in real time. The real server asks for your TOTP code. The fake site asks you for your TOTP code. You type it. The fake site relays it to the real server – immediately, within the 30-second window. The real server accepts it. The attacker now has an authenticated session.

This isn’t hypothetical. Tools like Evilginx and Modlishka automate the entire process – they act as transparent reverse proxies, sitting between you and the real server, relaying everything in both directions while capturing the authenticated session cookie. Evilginx is openly documented.

The fundamental weakness is this: a TOTP code has no cryptographic binding to the server’s identity. The code is valid no matter where you type it. It doesn’t know whether it’s being entered on the real site or a fake one. It proves you possess the secret. It says nothing about who you’re talking to.

SIM swap attacks. This one applies to SMS-based one-time passwords, not TOTP, but the distinction is important. An attacker calls your mobile carrier, convinces them (through social engineering or a bribed employee) to transfer your phone number to a new SIM card. Now the attacker receives your text messages – including any SMS verification codes.

This doesn’t affect app-based TOTP. The shared secret lives on your device’s storage, not in your phone number. No amount of SIM swapping can access it. This is one of the reasons NIST’s guidelines have flagged SMS as a weaker second factor compared to app-based TOTP or hardware tokens.

Malware and device compromise. If malware is running on the phone that holds your authenticator app, it can potentially read the shared secrets from the app’s storage or capture the displayed codes. TOTP assumes the device is trusted. If it isn’t, the “something you have” factor is compromised at its source.

Social engineering. “Hi, this is the security team. We’ve detected suspicious activity on your account. Could you read me the 6-digit code on your authenticator app so we can verify your identity?” It sounds absurd written down. It works disturbingly often in practice. The human factor remains the hardest to patch.

Phishing-resistant MFA

So TOTP can be phished in real time. Is there something better?

Yes. And the core insight is simple: the authenticator should know which server it’s talking to, and it should refuse to authenticate if the server is wrong.

FIDO2 and WebAuthn. The FIDO (Fast IDentity Online) Alliance, along with the W3C, developed a standard called WebAuthn that works like this: when you log in, the server sends a cryptographic challenge to your browser. Your browser passes this challenge to an authenticator – a hardware security key like a YubiKey, or a platform authenticator like Touch ID or Windows Hello. The authenticator signs the challenge using a private key that was generated during registration and that never leaves the authenticator.

Here’s the crucial part: the challenge includes the server’s origin – its domain name. The authenticator checks this origin before signing. If you’re on a phishing site (evil-bank.com instead of bank.com), the authenticator either refuses to sign (because it has no key registered for evil-bank.com) or signs a response that includes evil-bank.com as the origin, which the real bank.com server will reject.

Phishing doesn’t become harder. It becomes cryptographically impossible. The protocol won’t work with the wrong server, in the same way that the wrong key won’t turn the wrong lock. There’s no code to type, no code to relay, no 30-second window to exploit.

Passkeys. You may have seen this term. Passkeys are the consumer-friendly name for FIDO2 discoverable credentials – they can sync across your devices within an ecosystem (Apple’s iCloud Keychain, Google Password Manager, Microsoft’s credential store). They replace passwords entirely. You don’t type anything. You authenticate with your fingerprint, your face, or your device PIN, and the cryptographic handshake happens in the background.

The policy direction. It’s not just technologists saying this. NIST SP 800-63B and the US Office of Management and Budget’s M-22-09 – the federal zero-trust cybersecurity mandate – explicitly recommend phishing-resistant authenticators for high-assurance scenarios.

But let’s be fair to TOTP. TOTP is still a massive improvement over passwords alone. It stops credential stuffing dead – a stolen password from a breach is useless without the TOTP secret. It stops offline brute-force attacks. It stops casual, opportunistic account takeover. The adversary-in-the-middle attack requires significant effort, real-time interaction, and a convincing phishing site. Most attackers don’t bother when there are millions of accounts protected by nothing but a password.

TOTP isn’t perfect. But perfect is the enemy of good, and TOTP is genuinely good.

What MFA doesn’t protect

Honesty about boundaries is more useful than confidence about strengths. Here’s what MFA – any MFA, including the phishing-resistant kind – doesn’t cover.

Session hijacking after authentication. MFA protects the login. Once you’re authenticated and the server has issued you a session token (a cookie, a JWT, a bearer token), MFA is no longer in the picture. If an attacker steals that token – through cross-site scripting, a compromised browser extension, or network interception – they have your session. They don’t need to authenticate again. They already are you, as far as the server is concerned.

Unencrypted channels. If the connection between you and the server isn’t encrypted (no TLS, no HTTPS), then your TOTP code travels in plaintext. An attacker on the same network – a coffee shop Wi-Fi, a compromised router – can read it and replay it within the validity window. MFA assumes a secure channel underneath it.

Compromised servers. The shared secret is stored on both your device and the server. If the server’s database is breached and the secrets are stored in plaintext (or reversibly encrypted, which amounts to the same thing), the attacker can generate valid TOTP codes for every user in the database. RFC 4226 is clear on this: the server must protect stored secrets with strong encryption at rest. Not all implementations do.

Compromised endpoints. If malware has full control of your laptop or phone, it can see everything you see. It can read your password as you type it, capture your TOTP code as it’s displayed, and exfiltrate both. MFA can’t protect you from a device that’s already been taken over. The security model assumes that at least one factor’s device is trustworthy.

Physical access. Someone with physical access to your unlocked phone can open the authenticator app and read the codes. If the app doesn’t require its own authentication (a biometric or PIN to open it), the “something you have” factor is only as strong as the lock screen on the device.

These aren’t arguments against MFA. They’re arguments for understanding that security is layers, not walls. MFA is one layer – an important one – but it doesn’t work in isolation.

Where this all sits

Authentication is a spectrum, not a binary. At one end: no password at all, front door wide open. At the other: phishing-resistant MFA with hardware keys, encrypted channels, encrypted storage, endpoint monitoring, and a prayer.

Most of us live somewhere in the middle, and that’s fine. What matters is understanding what each layer does, what it protects against, and where it stops.

TOTP is elegant. It’s built on well-understood cryptographic primitives – HMAC, SHA-1, a shared secret, a time counter. It’s specified in open RFCs. It’s implemented in dozens of apps. It works across almost every service that supports MFA. The maths is beautiful in its simplicity: six lines of pseudocode, and an attacker who doesn’t know your secret can’t predict the next code.

It’s not perfect. The code can be phished in real time. The secret can be stolen from a breached server. The device can be compromised. But nothing is perfect. The question isn’t whether a control is flawless – it’s whether it raises the cost of attack high enough to make the attacker go elsewhere.

For most people, most of the time, TOTP does exactly that.

So: enable it everywhere you can. Prefer phishing-resistant options – passkeys, security keys – where they’re available. Use a password manager so your passwords are long, unique, and random. Encrypt your disks. Use HTTPS. Keep your devices updated.

And understand that security isn’t a product you buy or a box you tick. It’s a practice. Layers, not walls. Each one imperfect. All of them together, meaningfully better than any one alone.