Current & Trusted
Tutorial

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.

Andi Pratama
Andi Pratama
• • 2 min read
Share:
GTechUpdate Tech Banner
Foto: GTechUpdate Tech Banner

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.

Tag Terkait: #PHP
Andi Pratama

Andi Pratama

Contributing Editor

Spesialis keamanan siber, ethical hacker, dan analis kerentanan infrastruktur jaringan industri.

Related Articles

Lihat Semua →