The core concept behind all properly secured two-factor authentication systems involves the user proving their identity using some previously agreed upon mechanism that cannot reasonably be replicated.
The particular mechanism used by most ‘authenticator’ apps is known as TOTP (Time-based One Time Password). This is a modified variant of an older mechanism known as HOTP (HMAC One Time Password). Setup for either mechanism works as follows.
- When the user requests for a given service to enable 2FA, that service generates a secret to be used for the authentication purpose. The exact details of this generation don’t matter much here, and you can functionally think of the secret as a random number between 0 and some arbitrarily large number.
- The service then shares this number with the user, typically via a QR code encoding a special URL using the
otpauth://
scheme. The details of that are also not particularly important to the discussion, just know that it encodes the secret (and info about the site itself) in such a way that the authenticator device can read it.
- Once the authenticator reads the secret, it stores it (hopefully) securely together with info about the account provider (and typically some small comment to remind the user which account it is if they have more than one with that provider).
- The server then (hopefully) securely stores the secret and associates it with the user’s account. It may request one or more codes at this point to ensure that everything is working (and, in the case of HOTP, to ensure that the counter is in-sync, but more on that later).
Once that’s done, the authenticator is configured to provide codes for that account. Actually using the authenticator works as follows:
- When asked for a code, the authenticator first consults an internal counter. For HOTP, this is a simple counter that gets incremented by one for each code generated for a given account. For TOTP, it’s a timestamp counted in seconds since the UNIX epoch.
- After determining the counter, the authenticator retrieves the secret, combines it with the counter, and then generates a hashed message authentication code for the combination (typical implementations use SHA-1 for this, but any cryptographically secure hash algorithm can be used).
- If TOTP is being used, the timeout on the current code is displayed, and once it expires a new one is generated.
The server does exactly the same thing to verify the code.
It’s important to note that once the authenticator is configured, there is no communication the authenticator and the server other than the user entering a code when prompted, and there is no communication at all from the server to the authenticator. This aspect is an extremely important part of why HOTP and TOTP are generally considered secure but SMS or email-based 2FA are generally not.
It is additionally important to note that both HOTP and TOTP have a few inherent limitations:
- HOTP requires the client and server to keep their counters in-sync with each other. If they get out of sync (usually by the user generating multiple codes without using them), then the codes stop working. Many servers will handle the specific case of the client getting ahead of the server automatically up to some limit, and advance the counter to whatever it would be for the code that was used, but that still requires handling, and it adds a nontrivial processing overhead on the server.
- TOTP requires that the client and server generally agree relatively well on the time, and that the user uses the code before it expires. The first issue is generally solved for any internet connected client, but the second gets tricky and usually means that the server will accept a code for some time after it expires. The time-based nature also means that you need a lockout to prevent code reuse, which functionally rate-limits successful authentication attempts (this is not really an issue for any real world use case, and in fact improves security by a tiny bit, but it is a limitation).
Some systems use an approach similar to TOTP or HOTP but with a different algorithm, usually for legacy reasons. One particularly prominent example is Valve’s ‘Steam Guard Mobile Authenticator’, which functionally uses TOTP but fully automates the setup and uses a different mechanism to derive the code from the hash algorithm output.
For reference, the other major secure 2FA implementation that is in widespread usage is FIDO2, which works a bit differently but still relies on the concept of the user proving their identity by doing something that only they should be able to do (instead of generating codes, FIDO2 involves digitally signing an authentication challenge using a key that was previously configured with the server, which is technically more secure than TOTP for a couple of reasons).