Almost everyone reading this has typed a six-digit code into a box this week. Passwords are still the universal first factor, and a one-time password is the usual second — mostly because it is easy to implement and easy to explain. That ubiquity hides something worth saying plainly: OTP is not one control with five delivery options. It is several different mechanisms with different failure modes, and the choice between them is a choice about which failure you are prepared to carry.

Before any of the detail, one honest caveat. Security is relative and it moves. Every control adds friction, and friction is a real cost paid by real users. The point of what follows is not that one-time passwords are bad. It is to be specific about where each flavour goes wrong, so you can avoid the gap rather than inherit it.

And one thing is true of all of them. An OTP is a knowledge factor at the moment it is entered. Whatever produced it, the user types it into a page — and a user can be persuaded to type it into the wrong page. Every variant discussed here is phishable. That risk does not disappear by choosing differently among them; it has to be accounted for separately.

Three columns comparing OTP families. A delivered code, where the server generates and stores the code and sends it by email, SMS or push. HOTP, where a counter is shared and the risk is a wide brute-force window and counter rollover. TOTP, where a symmetric key is shared and stored on both server and device.
Three flavours, three different answers to the question of what is shared and what is stored.

The delivered code

The simplest form: the server invents a code and sends it to you. The variation is only in the channel, and the channel decides most of the trade-off.

Email

Easiest for everyone. No extra device, a well-understood deliverability model, plenty of providers, and cheap to send. The costs are latency between request and arrival, and the perennial fight with spam filters, which can be managed with care.

The third cost is the one that undermines the whole point. People reuse passwords across accounts. If the password protecting someone's mailbox is the same password protecting your application, an emailed code is not a second factor at all — it is the same factor, delivered twice. The mailbox is not independent of the login it is supposed to be reinforcing.

SMS

Genuinely a different possession factor, and that is worth something: the phone is additional proof of the device. Against that sit four things. Delivery can be slow. Not everyone has a phone. Some people decline to hand over a number that identifies them far more precisely than an email address does. And it costs — materially more than email, and the bill grows faster than you expect, helped along by providers whose incentives favour more traffic rather than less.

Push to your own app

Delivered through APNS or FCM to an app on a registered device. Same possession benefit as SMS, without the per-message bill. The cost moves rather than disappearing: you now build and maintain an application to receive them, which is a long-lived commitment rather than an integration.

The life of a delivered one-time password in four stages. Generated, where too short a code invites brute force. Delivered, where latency, spam filters and cost apply. Entered, where the user can be phished. Validated, where failure to purge the code leaves it live for another attempt.
The same code, four stages, four different things to get wrong.

Four decisions that apply whichever channel you pick

How long it lives. Long enough to survive delivery latency, short enough that an intercepted code is worthless by the time anyone uses it. Five to ten minutes has proven sufficient for most cases. Note what that implies: the server carries the state of that token for the whole window, in a database or in the session.

When it is destroyed. More important than the lifetime, and more often missed. A one-time password must not survive its one use — and that includes unsuccessful attempts. If a wrong guess leaves the code live, you have quietly converted a single-use secret into a retryable one.

How long it is. Short is kind to users and kind to attackers. Six digits is where most providers have landed, and it is a floor rather than an aspiration.

What it costs. Every delivered code is a recurring payment to a service provider. SMS especially. This is a security decision with a line in the budget, and pretending otherwise just means somebody else discovers it later.

HOTP: counting instead of sending

HOTP removes the delivery problem by agreeing a secret up front. The client and server share it, the server remembers the last counter value it saw, and a code validates if its counter is higher than that. Yubikey's implementation is the one most people have met.

The appeal is simplicity — no clock synchronisation to get wrong. The problems are three.

Because validity is defined by "greater than the last value seen" rather than by time, the window for brute force is wide. There are also few generic clients that support it, which limits where you can realistically use it. And then there is counter rollover: with Yubikey the counter wraps back to zero after 32,767 — sixteen bits — at which point previously used codes become valid again. A one-time password that becomes usable a second time is no longer a one-time password.

TOTP is the common choice, and for good reasons. It is a variant of HOTP where time replaces the counter: server and client share a symmetric key, the clock supplies the changing input, and the key is what makes two users' codes differ at the same instant. Codes last 30 or 60 seconds, and most servers accept the previous window too, to allow for someone typing slowly.

What you get is a standard algorithm with genuine client independence — Google Authenticator works against almost anything — and no per-message cost at all.

What you take on is less often discussed.

State management is hard, which in practice means a code can be attempted repeatedly for as long as its window is open. Rate limiting is not optional here; it is the control that makes the window safe, and it is extra work somebody has to do. The user also has to install an app, which is a real adoption cost for a non-technical audience.

And then the detail that belongs on every TOTP decision and rarely appears:

Two datastores compared. Passwords stored as one-way Argon2id hashes, which an attacker must still crack before use. TOTP symmetric keys stored in a directly usable form, which an attacker can use immediately to generate valid codes for every user.
Password hashes have to be cracked. TOTP keys do not.

The symmetric key is stored on the server, for every user, in a form the server can actually compute with. That is closer to storing passwords in plain text than to storing them properly. A leaked password store yields hashes: the attacker still has to crack them, and that costs time you can use. A leaked TOTP key store yields working secrets. Valid codes can be generated immediately, for every affected user, for as long as those keys remain unchanged.

This is the single biggest shortcoming of the method, and it is a storage problem rather than an algorithm problem. The key has to be readable to validate a code, so it should be protected the way a signing key is protected — not the way a password is.

So which one?

It depends on three things: how sensitive the resource is, who the users are, and what actually happens if an account is taken over.

For banking, SMS makes a strong case. It needs no setup from the customer, it reaches people who will never install an authenticator app, and it is additional evidence of the device. The cost is real but proportionate to what is being protected.

For a cost-conscious platform with technically comfortable customers, TOTP is attractive: no per-message bill, a standard the user may already have an app for. Take it on with your eyes open about the key store.

And whichever you choose, phishing applies to all of them. That risk has to be handled somewhere else in the design — not assumed away by picking a different flavour of the same kind of secret.