Implementing RFC 6238 TOTP Two-Factor Authentication (2FA) With No External Libraries
Understanding the Time-Based One-Time Password algorithm, and how to decode Base32 and build HMAC-SHA1 using PHP's built-in functions.
Securing accounts with an extra layer of verification is now standard. The TOTP algorithm used by authenticator apps works entirely offline, with no paid SMS or third-party service required.
How it works, step by step
The server and the authenticator app share a single secret key, exchanged once at enrollment, usually through a QR code. After that, the two never communicate again.
The code is generated by dividing the Unix timestamp by the 30-second interval to get an integer counter. That counter is hashed together with the secret key using HMAC-SHA1. The last four bits of the hash are used as an offset to select four bytes from the hash, from which the final six digits are taken. Because both sides compute from the same time, they arrive at the same number without exchanging anything.
Base32 and why it is used
Secret keys are exchanged in Base32 encoding, not Base64 or hexadecimal. The reason is practical: Base32 uses only uppercase letters and digits, avoids characters that are easy to confuse, and stays legible when a user has to type it in manually.
A Base32 decoder built from built-in functions is fairly straightforward — each character represents five bits, and those bits are concatenated and then split back into eight-bit bytes. Watch the handling of padding characters and the spaces often inserted for readability.
The details that determine its security
Three things are often missed. First, time tolerance: a user's device clock can drift, so the server should accept one interval before and after — more than that widens the guessing window for no real benefit.
Second, a code that has already been used must be rejected on the next attempt, because without that a stolen code stays valid until the end of its interval. Third, code comparison must use a constant-time function so it does not leak information through differences in response time.
The parts that are often forgotten
Limiting attempts matters as much as the algorithm itself. Six digits means a million possibilities; without rate limiting, that space can be searched far faster than most people expect.
Also provide single-use recovery codes and store them hashed, just like passwords. A user who loses their phone with no recovery path will call support — and a weak manual recovery process often becomes the weakest point in an entire system that was painstakingly built.
Andi Pratama
Contributing EditorSpesialis keamanan siber, ethical hacker, dan analis kerentanan infrastruktur jaringan industri.
Related Articles
Lihat Semua →Observability on a Tight Budget: Structured Logs, Metrics, and Just Enough Tracing
20 Sep 2026
Optimizing MySQL 8 on Shared Hosting
07 Sep 2026
Securing RESTful APIs with Token Bucket Rate Limiting and APCu
29 Aug 2026
The Anatomy of AI Image Generation: From Pixel Diffusion to Flow Matching
17 Oct 2026