toolhq.io

All posts
5 min readby Jameel Haider

How TOTP setup works: the otpauth URI, the shared secret, and QR codes

When you scan a QR code to add an account to an authenticator app, nothing secret travels over the camera that is not already on your screen. The whole handshake is one short text string, and understanding it demystifies two factor setup entirely.

The shared secret

TOTP, defined in RFC 6238, is a time based one time password. Both the server and your authenticator app hold the same secret key. Neither sends a code to the other during login: each independently computes the same 6 digit code from that shared secret and the current time, and the server simply checks that the code you typed matches the one it computed. No network round trip, which is why authenticator codes work on a plane.

Enrollment is the one moment the secret is transferred, from the server to your app, and that is what the QR code carries.

The otpauth URI

The QR code is not magic. It encodes a plain text URI in a format every authenticator understands:

otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30

Break it down:

  • otpauth://totp/ declares the type, TOTP rather than the counter based HOTP.
  • Example:alice@example.com is the label, usually issuer:account, so the app shows which service and which account.
  • secret is the shared secret, base32 encoded. This is the only sensitive part.
  • issuer names the service, shown in the app and used to avoid duplicate entries.
  • algorithm, digits, and period are the parameters below.

Scanning the code just reads this string. Typing the secret manually instead, which every setup flow offers as a fallback, provisions exactly the same account, which is why the secret is shown as base32: it is case insensitive and has no confusable characters, so it is safe to type by hand.

The parameters

ParameterCommon defaultMeaning
period30Seconds each code is valid before it rolls
digits6Length of the code, sometimes 8
algorithmSHA1HMAC hash used, rarely SHA256 or SHA512

The period is why codes refresh on a timer. The current time is divided into fixed windows of that length, the window number is fed through an HMAC keyed by the secret, and the result is truncated to the chosen number of digits. Because both sides use the same clock, they land on the same window and the same code.

A critical detail hides in that default: SHA1 is the near universal default, and most authenticator apps assume it. If a server issues SHA256 codes but the app defaults to SHA1, every code will be wrong even though the secret is correct. Keep the algorithm as the URI specifies.

Clock skew

Because the code depends on time, a device clock that drifts produces wrong codes. Servers handle this by accepting codes from the adjacent window on each side, typically one step of 30 seconds in either direction, so a small drift still works. Large drift on a phone whose clock is set manually is a common cause of codes that look right but never validate.

Why the QR code is safe to display

The QR code reveals the secret to anyone who photographs it, so it is a one time setup artefact, not a credential to keep. Treat the enrollment screen like a password: do not screenshot it into a shared album, and once scanned, the secret lives only in your app and the server.

Generate a TOTP secret and preview live codes against the period and digits you choose with the OTP Generator.