Skip to content

Repository files navigation

rfcotp

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.


Quick start

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 window

One-command full verification from a clean clone:

./proof.sh      # cargo tree + fmt + clippy + tests (debug & release) + --selftest

generate

rfcotp 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.

What's implemented, and against which vectors

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.

Security hygiene

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.

Limitations (by design)

  • 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.

Layout

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.

License

MIT OR Apache-2.0.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages