The crypto primitives Rust's standard library doesn't ship — implemented from their official specifications, and proven correct against the official published test vectors on every run.
rfcotp hand-rolls SHA-1, SHA-256, HMAC, PBKDF2, and Base32 in pure Rust with
zero third-party dependencies ([dependencies] is empty; cargo tree proves
it). Those primitives power an offline TOTP generator — the rotating 6-digit
codes that apps like Google Authenticator use for two-factor login.
This is not a password manager or a place to store secrets. It is a set of
standards-conformant primitives whose correctness you can check yourself: run
--selftest and watch every primitive pass or fail against the same numbers
cryptographers everywhere use to check their own implementations.
Not a substitute for an audited crypto library. The point here is verifiability, not production hardening.
cargo build --release
# 1. Prove every primitive against its official RFC/FIPS test vectors
./target/release/rfcotp --selftest # exits 0 iff every vector passes
# 2. Generate a TOTP code from a Base32 secret
./target/release/rfcotp generate --secret JBSWY3DPEHPK3PXP
./target/release/rfcotp generate --secret JBSWY3DPEHPK3PXP --watch # live, refreshes each windowOne-command full verification from a clean clone:
./proof.sh # cargo tree + fmt + clippy + tests (debug & release) + --selftestrfcotp generate (--secret <BASE32> | --secret-file <PATH> | --secret-env <VAR>)
[--algorithm sha1|sha256] # default sha1
[--digits 6..8] # default 6
[--step <SECONDS>] # default 30
[--time <UNIX>] # default: now
[--watch] # reprint each window; Ctrl-C to stop
The secret is decoded leniently (lowercase, spaces, and missing = padding are
all accepted), held in a memory buffer that is zeroed after use, and never
printed or logged.
| Primitive | Spec | Verified against |
|---|---|---|
| SHA-1 | RFC 3174 / FIPS 180-4 | RFC 3174 §7.3, FIPS 180-4, NIST (empty), 1 M × "a" |
| SHA-256 | FIPS 180-4 | FIPS 180-4 Appendix B, NIST (empty), 1 M × "a" |
| HMAC | RFC 2104 | RFC 2202 (HMAC-SHA-1 TC1–7), RFC 4231 (HMAC-SHA-256 TC1–7) |
| Base32 | RFC 4648 §6 | RFC 4648 §10 (all 7 rows, encode + decode) |
| PBKDF2 | RFC 2898 / RFC 8018 | RFC 6070 (all 6 SHA-1 vectors), RFC 7914 §11 (SHA-256) |
| HOTP | RFC 4226 | RFC 4226 Appendix D (counters 0–9: HMAC, truncation, code) |
| TOTP | RFC 6238 | RFC 6238 Appendix B (6 timestamps × SHA-1 and SHA-256) |
--selftest runs 65 vectors across these 7 specs and reports pass/fail per
vector. That output is the reproducible evidence — no interaction, no domain
expertise required to read it.
Passing the vectors proves the primitives are correct. Two things a "just pass the vectors" implementation would skip, folded in here:
- Constant-time comparison (
src/ct.rs) — every place a code or MAC is checked against an expected value uses a non-short-circuiting compare, never==, so it doesn't leak how many leading bytes matched. A differential test asserts it agrees with==on 500 random inputs, in debug and release. - Secret zeroization (
src/zeroize.rs) — HMAC/PBKDF2 key material and the decoded TOTP secret are overwritten with volatile writes as soon as they're done with, not left for the allocator to recycle.
proof.sh includes a grep gate that fails the build if a raw == on secret or
MAC material appears outside ct.rs.
- No SHA-512 (and so no SHA-512 TOTP variant).
- No QR-code image output — that needs an image-encoding dependency this project
deliberately doesn't have.
otpauth://URI text only, if added. - Offline only. No network code anywhere.
- Not constant-time in the hash cores themselves — only in the comparison paths.
src/
sha1.rs sha256.rs hashes (streaming + one-shot)
hash.rs Hash trait + shared Merkle–Damgård padding
hmac.rs HMAC, generic over Hash
base32.rs RFC 4648 encode / strict + lenient decode
pbkdf2.rs PBKDF2 over HMAC
hotp.rs totp.rs HOTP / TOTP + constant-time verify
ct.rs zeroize.rs constant-time compare, zeroization
vectors.rs every official test vector, one source of truth
selftest.rs the --selftest harness
cli.rs main.rs argument parsing, `generate`
tests/integration.rs cross-module + spawned-binary tests
proof/ committed cargo tree / selftest / test output
See BUILD_PLAN.md for the phase-by-phase build history and STDLIB.md for the per-primitive "what crate this replaces" log.
MIT OR Apache-2.0.