totp-lite
A minimal, RFC 6238-compliant TOTP library for Rust with pluggable SHA-1, SHA-256, and SHA-512 hashing.
Repository Health
Technical Analysis
totp-lite is a small, focused Rust crate that implements Time-based One-Time Password generation per RFC 6238. It exposes two functions — totp for the common case (30-second step, 8-digit output) and totp_custom for full control over step and digit count — parameterized by hash algorithm (SHA-1, SHA-256, SHA-512) via generic type parameters backed by the RustCrypto digest/hmac crate ecosystem.
At roughly 140 lines of source and a small, well-known dependency chain (digest, hmac, sha1, sha2), it’s designed to be embedded directly into authentication flows — two-factor auth, API key rotation, or Google-Authenticator-compatible clients — without pulling in a heavier crypto or auth framework.
What You Get
- RFC 6238-compliant TOTP generation via the
totpandtotp_customfunctions - Support for SHA-1, SHA-256, and SHA-512 as the underlying HMAC hash algorithm, selected via a generic type parameter
- Configurable time step and output digit count through
totp_custom - A working example (
examples/ga.rs) showing a Google Authenticator-compatible flow with base32 secret decoding - A tiny dependency surface built entirely on the RustCrypto
digest/hmaccrate family
Common Use Cases
- Two-factor authentication for a web app - a backend generates and verifies 6-8 digit TOTP codes during login to add a second authentication factor.
- Google Authenticator-compatible client - a CLI or mobile app decodes a base32 secret and produces codes matching what Authenticator apps display, as shown in
examples/ga.rs. - API key rotation - a service issues short-lived, time-boxed tokens instead of static API keys, using
totp_customto tune the digit count and step interval. - Embedded auth in an existing RustCrypto stack - projects that already depend on
digest/hmac/sha2add TOTP support without introducing a new hashing implementation.
Under The Hood
Architecture
The crate is a single file (src/lib.rs, ~140 lines) exposing two public functions, totp and totp_custom, plus a private to_bytes helper — there are no modules, layers, or state to speak of. totp delegates to totp_custom with RFC-default constants (DEFAULT_STEP = 30, DEFAULT_DIGITS = 8), making totp_custom the sole implementation of the RFC 6238 algorithm: it computes an HMAC over the big-endian time-step counter using a generic hash type constrained by digest’s CoreProxy/FixedOutputCore traits, then applies RFC dynamic truncation — using the last hash byte as an offset into the digest to extract a 31-bit integer, mod-reduced to the requested digit count. The generic trait bounds exist purely to satisfy Hmac<H>’s requirements across SHA-1/256/512’s differing block sizes, giving compile-time hash-algorithm polymorphism rather than runtime dispatch.
Tech Stack
A Rust 2018 edition crate with a deliberately small dependency set drawn entirely from RustCrypto: digest 0.10 for hash trait definitions, hmac 0.12 for the HMAC construction, and sha1/sha2 0.10 for the concrete hash implementations re-exported as Sha1, Sha256, and Sha512. Dev-dependencies are version-sync 0.9, which keeps the doc(html_root_url) attribute in sync with the crate version via tests/version-numbers.rs, and koibumi-base32, used only by the examples/ga.rs demo. There’s no build script, FFI, or async runtime — this is a pure computation library, published to crates.io and built with cargo test --all-targets on GitHub Actions CI.
Code Quality
Tests live inline in src/lib.rs under #[cfg(test)] mod tests, encoding the official RFC 6238 Appendix B test vectors for SHA-1, SHA-256, and SHA-512 — strong evidence of correctness against the spec rather than just internal consistency. Error handling is minimal by design: Hmac::new_from_slice is unwrapped rather than propagated as a Result, acceptable since HMAC in this crate family accepts keys of any length, but a latent panic surface if that ever changes upstream. Naming is clear and idiomatic, and hash-algorithm selection is encoded in the type system via generics rather than a string or enum, making misuse a compile error rather than a runtime one. No lint configuration is checked in, but CI runs the full test suite including doctests, and version-sync guards documentation drift.
API Design
The public surface is exactly two functions, both generic over hash algorithm — about as low-boilerplate as an auth-code library gets: totp::<Sha512>(secret, seconds) returns a String directly, with no builder, config struct, or initialization step. Every public function’s doc comment includes a runnable, RFC-verified doctest, so the documented examples can’t silently drift from actual behavior. The one friction point is for callers who want to be generic over hash type themselves — the where H: Update + FixedOutput + CoreProxy, ... bound is copied from digest’s internals and isn’t trivial to satisfy for a custom hash type — but for the common case of using the three re-exported hash types, callers never see this.