Security

Two-Factor Authentication with TOTP

·2 min read

TOTP — Time-based One-Time Password — is the six-digit code your authenticator app generates. It's a thirty-second second factor and the realistic minimum for any account that matters. The protocol is small, the threat model is well-understood, and self-hosting is a one-evening project.

How TOTP works

Defined in RFC 6238. A shared secret (typically 160 bits, base32-encoded), HMAC-SHA1 of the secret with the current Unix time divided by 30, truncated to six digits. The server stores the secret and computes the same thing; if it matches, you're in. The "QR code" you scan during enrollment encodes the secret as an otpauth:// URI.

otpauth://totp/Site:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Site

Authenticator apps

Three reasonable choices:

  • Aegis (Android) / Raivo (iOS, archived): open-source, encrypted exportable vault. The right answer.
  • Bitwarden: TOTP inside your password manager. Convenient; arguably defeats the "second factor" premise.
  • Google Authenticator / Authy: closed-source, harder to back up. Avoid.

Back up the seeds

Lose your phone with no backup and you're locked out of every account that requires TOTP. Two patterns:

  • Use an authenticator with encrypted export (Aegis exports an encrypted JSON file).
  • When enrolling, write down the secret (the base32 string under the QR code) and store it offline. You can re-enroll from this on any TOTP app.

TOTP for SSH and sudo on Linux

The Google PAM module gives you TOTP at login time:

apt install libpam-google-authenticator

# Per-user enrollment
google-authenticator
# Walk through the prompts (time-based: yes, update file: yes, rate-limit: yes)
# Save the QR code or secret to your authenticator
# /etc/pam.d/sshd — add at the top
auth required pam_google_authenticator.so

# /etc/ssh/sshd_config
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

AuthenticationMethods publickey,keyboard-interactive means "key AND TOTP" — both required.

Test in a second terminal before logging out.

A PAM misconfiguration locks you out. Keep an active session open while you verify the next login works.

Self-hosting

If you want a centralized 2FA-aware identity provider for your services, Authentik, Keycloak, or Authelia all support TOTP plus OIDC/SAML. Run one as a forward-auth provider behind your reverse proxy and every internal service gets 2FA without per-service config.

TOTP isn't perfect

TOTP defeats password-only theft and credential stuffing. It does not defeat real-time phishing — a fake login page can relay your code to the real one within the 30-second window. For accounts that genuinely matter (cloud root, code-signing, finance), use a hardware key (WebAuthn / FIDO2 / YubiKey) that signs the actual origin and can't be tricked by a phishing page.

Related