totp

package
v0.0.0-...-2e1560f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 11 Imported by: 0

README

totp

Purpose

totp implements RFC 6238 Time-Based One-Time Passwords for Eshu's authenticator-app MFA factor (issue #4986). It is a small, dependency-free package: standard library only (crypto/hmac, crypto/sha1, crypto/subtle, crypto/rand), no database, HTTP, or storage concerns.

Before this package, Eshu's only MFA factor was recovery codes (identity_mfa_recovery_codes); there was no runtime TOTP verification. This package is the cryptographic primitive go/internal/storage/postgres (sealed-secret storage) and go/internal/query (enrollment/login endpoints) build on.

Exported surface

  • GenerateCode(secret []byte, t time.Time, step time.Duration, digits int) (string, error) — RFC 4226 HOTP dynamic truncation over the RFC 6238 time-step counter, keyed by HMAC-SHA1. digits must be 6-8.
  • Verify(secret []byte, code string, t time.Time, step time.Duration, digits int, skewSteps int) (bool, error) — checks code against a +/- skewSteps window of time steps around t, using crypto/subtle.ConstantTimeCompare. An empty code returns false, nil, never an error.
  • GenerateSecret() ([]byte, error) — a fresh 20-byte (HMAC-SHA1-sized) crypto/rand secret.
  • ProvisioningURI(ProvisioningURIParams) (string, error) — builds the otpauth://totp/... URI an authenticator app decodes from a QR code (the de facto "Key Uri Format"). This package never renders a QR image; the console renders the QR client-side from this URI text.
  • DefaultDigits (6), DefaultStep (30s), DefaultSkewSteps (1) — the approved production defaults (issue #4986 design).

RFC conformance

totp_test.go verifies GenerateCode against the literal RFC 6238 Appendix B SHA1 test vectors (the RFC's own worked table, 8-digit truncation, secret "12345678901234567890"):

Unix time Expected (8-digit)
59 94287082
1111111109 07081804
1111111111 14050471
1234567890 89005924
2000000000 69279037
20000000000 65353130

A second test (TestGenerateCode_SixDigitIsSuffixOfEightDigit) proves the production 6-digit path is the same computation: HOTP truncation is binary_code mod 10^digits, and because 10^6 divides 10^8, (x mod 10^8) mod 10^6 == x mod 10^6 — so the 6-digit code is always the last six characters of the 8-digit RFC vector for the same input. This ties the 6-digit production default back to the same RFC ground truth without a separately-sourced (and easier to get wrong from memory) 6-digit vector table.

Secret handling

This package never persists, seals, or opens a secret. GenerateSecret returns raw bytes; every caller is responsible for:

  • sealing the secret at rest immediately (see go/internal/secretcrypto) before it is written to any column,
  • opening it again only inside the single Verify call on the login or enroll-confirm path, and
  • never logging, returning via a read API, or otherwise persisting the plaintext.

Where this fits

flowchart LR
  A["GenerateSecret"] --> B["ProvisioningURI (enrollment QR)"]
  A --> C["secretcrypto.Seal (caller-owned)"]
  C --> D["sealed secret column (caller-owned)"]
  D -->|"secretcrypto.Open (caller-owned)"| E["Verify (login / enroll-confirm)"]

Dependencies

Standard library only. No internal package imports.

Telemetry

None. This is a pure computation package with no I/O; telemetry for the enrollment and login paths that call it belongs to those callers.

Documentation

Overview

Package totp implements RFC 6238 Time-Based One-Time Passwords (TOTP) for Eshu's authenticator-app MFA factor (issue #4986), using only the Go standard library: crypto/hmac and crypto/sha1 for the RFC 4226 HOTP primitive, crypto/rand for secret generation, and crypto/subtle for constant-time code comparison.

GenerateCode computes one HOTP value (RFC 4226 Section 5.3 dynamic truncation) over the RFC 6238 Section 4.2 time-step counter (floor(unixSeconds / step)), keyed by HMAC-SHA1. It is verified against the literal RFC 6238 Appendix B SHA1 test vectors in totp_test.go.

Verify checks a submitted code against a window of +/- skewSteps time steps around now (RFC 6238 Section 5.2 recommends tolerating a small amount of clock drift), using crypto/subtle.ConstantTimeCompare so a timing side channel cannot narrow down which digit of a guessed code was wrong.

GenerateSecret returns a fresh crypto/rand secret sized for HMAC-SHA1 (20 bytes, matching the RFC 4226 Appendix C reference secret length). ProvisioningURI builds the otpauth:// URI an authenticator app QR code encodes; this package never renders a QR image itself — callers (the console) render the QR client-side from the URI text.

This package holds no database, HTTP, or storage concerns and never receives an already-sealed secret: callers own sealing the generated secret at rest (see go/internal/secretcrypto) and only ever pass this package the opened plaintext for the single verification call.

Index

Constants

View Source
const (
	// DefaultDigits is the production TOTP code length (approved design:
	// 6 digits, matching every mainstream authenticator app).
	DefaultDigits = 6
	// DefaultStep is the production TOTP time step (approved design: 30s,
	// the RFC 6238 recommended default).
	DefaultStep = 30 * time.Second
	// DefaultSkewSteps is the production clock-skew tolerance: one step
	// before and after the current step (approved design: +/-1 step).
	DefaultSkewSteps = 1
)

Variables

This section is empty.

Functions

func GenerateCode

func GenerateCode(secret []byte, t time.Time, step time.Duration, digits int) (string, error)

GenerateCode computes the RFC 6238 TOTP code for secret at time t, using the given step duration and digit count. It implements RFC 4226 Section 5.3 HOTP dynamic truncation over the RFC 6238 Section 4.2 time-step counter (floor(t.Unix() / step.Seconds())), using HMAC-SHA1 per the RFC 6238 default and only supported mode in this package.

digits must be 6, 7, or 8 (RFC 4226 Section 5.3 requires at least 6; this package caps at 8, the widest value any RFC 6238 vector or common authenticator app uses). Verified against the RFC 6238 Appendix B SHA1 test vectors in totp_test.go.

func GenerateSecret

func GenerateSecret() ([]byte, error)

GenerateSecret returns a fresh, cryptographically random shared secret sized for HMAC-SHA1 TOTP (secretLengthBytes bytes). Callers seal the returned bytes at rest before persisting (see go/internal/secretcrypto) and open it again only inside the single verification call; this package never persists or transmits the secret itself.

func ProvisioningURI

func ProvisioningURI(params ProvisioningURIParams) (string, error)

ProvisioningURI builds the otpauth://totp/... provisioning URI a standards-compliant authenticator app (Google Authenticator, Authy, 1Password, etc.) decodes from a QR code, per the de facto "Key Uri Format" convention (https://github.com/google/google-authenticator/wiki/Key-Uri-Format). This package renders no QR image; the console renders the QR client-side from this URI text so the plaintext secret only ever exists in the browser and the single server-side verification call, never in a server-generated image.

func Verify

func Verify(secret []byte, code string, t time.Time, step time.Duration, digits int, skewSteps int) (bool, error)

Verify reports whether code matches secret at time t within +/- skewSteps time steps (RFC 6238 Section 5.2 recommends tolerating a small amount of clock drift between the authenticator app and the server). It checks the current step first, then increasingly distant steps, and compares every candidate with crypto/subtle.ConstantTimeCompare so no timing signal leaks which candidate (if any) matched.

An empty code always returns false, nil (never an error) so callers can treat "no code submitted" identically to "wrong code" without a type switch. skewSteps must be non-negative.

Types

type ProvisioningURIParams

type ProvisioningURIParams struct {
	// Issuer identifies the service in the authenticator app's entry list
	// (for example "Eshu"). Required; must not contain ':'.
	Issuer string
	// Account identifies the enrolling user (for example an email or
	// username). Required; must not contain ':'.
	Account string
	// Secret is the raw (unsealed) shared secret this URI encodes. Required.
	// Callers must only pass a freshly generated or freshly opened secret —
	// this function never touches storage.
	Secret []byte
	// Digits is the code length the authenticator app should generate.
	// Defaults to DefaultDigits when zero.
	Digits int
	// Period is the TOTP time step the authenticator app should use.
	// Defaults to DefaultStep when zero.
	Period time.Duration
}

ProvisioningURIParams names the fields ProvisioningURI encodes into an otpauth:// URI. Digits and Period default to DefaultDigits and DefaultStep when zero, so callers enrolling with the production defaults can leave them unset.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL