How TOTP works, and how HostSpica Authenticator implements it
SHORT ANSWER
How does a TOTP authenticator app generate a code without internet?
It shares a secret key with the service once, at setup. To make a code it takes the current time, divides it into 30-second steps, runs an HMAC of that step number with the secret, and cuts the result down to 6 digits. The service repeats the same maths, so the codes match without any network connection.
Key takeaways
- TOTP (RFC 6238) is HOTP (RFC 4226) with the counter replaced by the number of time steps since 1 January 1970.
- The whole algorithm is a few lines of standard-library crypto. HostSpica Authenticator uses only `javax.crypto`, with no third-party OTP library.
- You can check any implementation against the RFC test vectors. We list them below.
- TOTP does not stop real-time phishing, and anyone who copies the secret can make your codes. It is a second factor, not a replacement for a strong first one.
The idea in one paragraph
When you add an account to an authenticator app, the service gives you a secret key, usually as a QR code. Both sides now hold the same secret. After that, neither side needs to talk to the other: each one computes the current code from the secret and the clock, and the service checks that your code equals its own. That is why an authenticator works in airplane mode.
The algorithm, step by step
- Work out the time step:
T = floor(unix_time / period). The period is 30 seconds for almost every service, so every 30 seconds T goes up by one. - Write T as an 8-byte big-endian number.
- Compute an HMAC over those 8 bytes, keyed with the secret. The hash is SHA-1 by default; SHA-256 and SHA-512 are also allowed by the RFC.
- Dynamic truncation (RFC 4226 section 5.3): take the last 4 bits of the HMAC output as an offset, read 4 bytes starting there, and clear the top bit so the result is a positive 31-bit number.
- Take that number modulo 10 to the power of the digit count (6 by default, 8 is also used) and pad with leading zeros.
Because the HMAC output looks random and the offset moves around, knowing a few codes tells an attacker nothing useful about the secret.
What HostSpica Authenticator runs
The Authenticator code is a direct translation of that list. It uses only javax.crypto.Mac, so there is no OTP library to trust or update. This is the core of it:
fun generate(secret: ByteArray, counter: Long, digits: Int = 6, algorithm: HmacAlgorithm = HmacAlgorithm.SHA1): String {
val counterBytes = ByteBuffer.allocate(8).putLong(counter).array()
val mac = Mac.getInstance(algorithm.macName)
mac.init(SecretKeySpec(secret, algorithm.macName))
val hash = mac.doFinal(counterBytes)
val offset = hash[hash.size - 1].toInt() and 0x0F // dynamic truncation
val binary = ((hash[offset].toInt() and 0x7F) shl 24) or
((hash[offset + 1].toInt() and 0xFF) shl 16) or
((hash[offset + 2].toInt() and 0xFF) shl 8) or
(hash[offset + 3].toInt() and 0xFF)
return (binary % 10.pow(digits)).toString().padStart(digits, '0')
}TOTP is then one more line: the counter passed in is (now - 0) / period. The app supports SHA-1, SHA-256 and SHA-512, 6 to 8 digits, and custom periods, because services that deviate from the 30-second default still follow the same RFC. Counter-based HOTP accounts use the same function with a stored counter, and the app can resynchronise a drifted counter by searching a window of the next 10 values for the code you type.
Where the secret lives
The secret is stored encrypted with AES-256-GCM under a key that Android Keystore generates and never lets out of secure hardware. It exists in plain form only for the moment it takes to compute a code. See Keystore key hierarchy for the full picture.
What TOTP does not protect against
Why SHA-1 is still the default
Nearly every service issues SHA-1 TOTP secrets, so apps must support it. The attacks that broke plain SHA-1 collision resistance do not apply to HMAC-SHA1 in the same way, which is why the RFC-based use is still considered acceptable. Where a service offers SHA-256 or SHA-512, our app uses it.
Frequently asked questions
Why do codes change every 30 seconds?
The period is part of the setup. The counter is the number of 30-second steps since 1970, so when the step number changes, the code changes. Services accept a tiny window around the current step to allow for small clock differences.
What if my phone's clock is wrong?
Codes are computed from the clock, so a clock that is off by more than the service's tolerance will produce rejected codes. Turn on automatic date and time in Android settings.
Is a TOTP code the same as an SMS code?
No. A TOTP code is generated on your device from a shared secret and never travels over the phone network, so it cannot be intercepted in transit or redirected by a SIM swap.
References
Review status
Last technical self-review by the author on 3 October 2026. No independent reviewer yet. If you spot an error, write to [email protected] and we will correct it and note the change.
Rohan builds HostSpica's Android apps — Authenticator, Passkey and Identity — and writes up how they work, including the mistakes along the way.
ABOUT THE PRODUCTS
RELATED