What Is TOTP?
TOTP stands for Time-Based One-Time Password. It is a widely adopted algorithm (standardized in RFC 6238) that generates short-lived numeric codes used as a second authentication factor. You've seen these codes in Google Authenticator, Authy, or Microsoft Authenticator — a six-digit number that changes every 30 seconds.
Unlike a static password that can be stolen and reused indefinitely, a TOTP code is valid for only one time window (typically 30 seconds). Even if an attacker intercepts a code, it becomes useless the moment that window expires.
How TOTP Works — The Algorithm
TOTP is built on top of HOTP (HMAC-Based One-Time Password, RFC 4226), replacing the counter variable with a time-derived counter. Here's the full computation:
- Shared secret — During enrollment, your app and the authentication server exchange a secret key (typically Base32-encoded, e.g.,
JBSWY3DPEHPK3PXP). This secret never leaves the device after setup. - Time counter — The current Unix timestamp is divided by the time step (30 seconds by default):
T = floor(unix_time / 30) - HMAC-SHA1 — The time counter is hashed with the secret key using HMAC-SHA1:
hmac = HMAC-SHA1(secret, T) - Dynamic truncation — A 4-byte chunk is extracted from the HMAC output using offset-based truncation, producing a 31-bit integer.
- Modulo — The integer is reduced modulo 10^6 to produce a 6-digit code:
code = integer % 1_000_000
Both the client (your phone app) and the server perform this same computation independently. If the resulting codes match, authentication succeeds — no network exchange of the code is required between client and server during the auth flow itself.
# Pseudocode for TOTP generation
import hmac, hashlib, struct, time, base64
def generate_totp(secret_base32: str, digits=6, step=30) -> str:
secret = base64.b32decode(secret_base32.upper())
counter = int(time.time()) // step
msg = struct.pack(">Q", counter) # 8-byte big-endian counter
h = hmac.new(secret, msg, hashlib.sha1).digest()
offset = h[-1] & 0x0F
code = struct.unpack(">I", h[offset:offset+4])[0] & 0x7FFFFFFF
return str(code % (10 ** digits)).zfill(digits)
TOTP vs HOTP: What's the Difference?
HOTP generates codes based on a counter that increments with each use. TOTP replaces that counter with the current time. This creates a key tradeoff:
- HOTP: Codes don't expire until used. Works offline indefinitely. But counter synchronization can drift if many codes are generated without verification.
- TOTP: Codes expire every 30 seconds. Much harder for attackers to use intercepted codes. Requires rough clock synchronization between client and server (RFC 6238 allows a 1–2 window tolerance to handle slight drift).
TOTP is the dominant standard for authenticator apps precisely because its short validity window dramatically limits the utility of phishing attacks.
The Secret Key: Your TOTP Seed
The secret key is the foundation of TOTP security. It is typically:
- 80–160 bits of cryptographically random data
- Encoded as Base32 for human readability (e.g.,
JBSWY3DPEHPK3PXP) - Shared exactly once during enrollment, often via a QR code
The otpauth URI format encodes all enrollment parameters:
otpauth://totp/GitHub:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&digits=6&period=30
Scanning this as a QR code in any RFC 6238-compliant app (Google Authenticator, Authy, Bitwarden) will set up TOTP for that account.
Security Considerations for Developers
Protect the secret key at rest
The secret key is as sensitive as a password. Store it encrypted in your database (not plain text). If an attacker obtains the secret key, they can generate valid codes indefinitely. Use your application's encryption-at-rest strategy (e.g., AES-256-GCM with a key stored in a KMS).
Enforce HTTPS
TOTP codes are transmitted from the user's browser to your server during login. Always use HTTPS to prevent interception. A code intercepted over HTTP can be replayed within the 30-second window.
Rate limiting and code replay prevention
Implement rate limiting on TOTP submission (e.g., max 5 attempts before lockout). Track recently used codes server-side and reject reuse within the same window to prevent replay attacks.
Backup codes
Always generate a set of single-use backup codes during enrollment. Users who lose access to their authenticator app need a recovery path. Store hashed backup codes, not plain text.
Clock drift tolerance
RFC 6238 recommends accepting codes from the current window and the immediately adjacent windows (±1 step, so ±30 seconds). This accounts for slight clock differences between client and server. Don't extend this tolerance unnecessarily — a wider window gives attackers more time.
Implementing TOTP in Popular Languages
Every major language has a battle-tested TOTP library:
- Node.js:
otplib,speakeasy - Python:
pyotp - Go:
github.com/pquerna/otp - Java:
GoogleAuth(Warrenstrange) - PHP:
spomky-labs/otphp
Use a library rather than rolling your own TOTP implementation. Cryptographic libraries handle edge cases (Base32 padding, big-endian encoding, clock skew tolerance) that are easy to get wrong.
Testing TOTP During Development
Use Copynix's TOTP generator to verify your implementation during development. Paste your test secret key and confirm the generated codes match what your server accepts. This is especially useful when testing TOTP integration before a production rollout.
Summary
TOTP is a proven, widely supported 2FA standard that dramatically improves account security without requiring internet connectivity on the authenticator side. Understanding the underlying algorithm — HMAC-SHA1, time-derived counters, and dynamic truncation — helps you implement it correctly and debug issues when they arise.
The most important implementation details: secure secret key storage, HTTPS enforcement, rate limiting, and backup code generation. Get these right and TOTP becomes a robust second factor for your users.
