totp-rfc
Strict, constant-time HOTP and TOTP primitives for Rust.
totp-rfc implements the HMAC-based one-time password algorithm from
RFC 4226 and the time-based one-time
password algorithm from RFC 6238.
It is designed for security-sensitive 2FA and MFA systems that need a small,
auditable, no_std foundation instead of provisioning, QR-code, or storage
abstractions.
Why totp-rfc?
- Exact RFC 4226 HOTP and RFC 6238 TOTP behavior
- HMAC-SHA-1, HMAC-SHA-256, and HMAC-SHA-512 through
RustCrypto - Six-, seven-, and eight-digit decimal one-time passwords
- Mandatory 128-bit minimum secret length
- Strict ASCII input parsing with preserved leading zeroes
- Constant-time comparison for well-formed authentication codes
- Checked 64-bit counters and timestamps beyond the year 2038
- Bounded HOTP resynchronization and TOTP clock-drift windows
- Borrowed secrets and zeroized transient HMAC/hash state
no_std, no allocation, and no unsafe Rust
The RFC compliance matrix maps each protocol requirement to its implementation and test evidence.
Installation
Add the crate with Cargo:
cargo add totp-rfc
Or add it directly to Cargo.toml:
[]
= "0.1"
TOTP example
The default configuration is HMAC-SHA-1, six digits, a 30-second period, and
Unix epoch T0 = 0:
use ;
let secret = new.unwrap;
let totp = default;
let code = totp.generate.unwrap;
assert_eq!;
let matched = totp
.verify_window
.unwrap
.unwrap;
assert_eq!;
assert_eq!;
For eight-digit HMAC-SHA-256 TOTP:
use ;
let secret = new.unwrap;
let totp = new.unwrap;
assert_eq!;
HOTP example
use ;
let secret = new.unwrap;
let hotp = default;
assert_eq!;
let matched = hotp
.verify_window
.unwrap
.unwrap;
assert_eq!;
assert_eq!;
Supported protocol parameters
| Primitive | Algorithms | Digits | Moving factor |
|---|---|---|---|
| HOTP | HMAC-SHA-1 | 6, 7, 8 | 64-bit event counter |
| TOTP | HMAC-SHA-1, SHA-256, SHA-512 | 6, 7, 8 | 64-bit Unix time-step counter |
All counters are encoded as unsigned, eight-byte, big-endian values. TOTP uses
T = floor((timestamp - T0) / period) and rejects timestamps before T0.
Security boundary
This crate calculates and matches one-time passwords. A production validation service must still:
- generate a unique cryptographically random secret for every credential;
- encrypt secrets at rest and restrict access to decrypted key material;
- throttle failures across sessions and distributed server instances;
- atomically persist
HotpMatch::next_counter()after HOTP success; - record accepted TOTP counters and reject reuse after successful validation;
- use secure transport and an independent authentication factor;
- keep resynchronization and clock-drift windows as small as possible.
The Secret wrapper borrows key material and does not own or retain it.
RustCrypto's zeroization support clears transient HMAC and hash state on drop.
Long-term storage encryption, memory locking, and caller-owned key zeroization
remain application responsibilities.
Use at least 20 random bytes for SHA-1, 32 for SHA-256, or 64 for SHA-512. The API enforces RFC 4226's mandatory minimum of 16 bytes.
no_std
Disable default features for embedded and core-only environments:
[]
= { = "0.1", = false }
The default std feature only adds implementations of std::error::Error.
Cryptographic calculation requires neither allocation nor access to the system
clock.
Verification quality
The repository tests:
- every RFC 4226 Appendix D HOTP vector;
- all SHA-1, SHA-256, and SHA-512 vectors from RFC 6238 Appendix B;
- time, counter, parsing, overflow, drift, and replay-state boundaries;
- default-feature and
no_stdconfigurations; - the declared Rust 1.85 minimum version;
- every viable mutation generated across the library.
Run the resource-bounded mutation sweep:
./scripts/mutation-test.sh
Run the Criterion primitive benchmarks:
nice -n 10 cargo bench --bench primitives --offline -j 1
Both suites deliberately limit concurrency and execution time.
Minimum supported Rust version
The MSRV is Rust 1.85.0 and is checked in CI. An MSRV increase is treated as a user-visible compatibility change and will be documented in the changelog.
Project scope
Base32 encoding, otpauth:// URIs, QR codes, random-secret generation, system
clock access, databases, distributed replay locks, and login throttling belong
in provisioning or service layers. Keeping those concerns outside this crate
preserves a compact, portable, and auditable RFC primitive layer.
Contributing
Contributions are welcome. Read CONTRIBUTING.md before submitting changes. Security reports must follow SECURITY.md.
License
Licensed under the MIT License.