Rust · no crypto crates · known-answer tested
Gatekeeper is a token authentication toolkit built from scratch: SHA-256 and HMAC-SHA256 written by hand, signed tokens with claims and expiry, and salted password hashing. No black box. Here is the real engine, running in your browser.
running SHA-256 / HMAC-SHA256 self-check against RFC 4231 vectors…
Every step below is code you can read, no dependency does the cryptography for you.
The compression function, message schedule, and padding are written from the FIPS 180-4 spec, no crates.
Key padding, inner and outer hash, built directly on the SHA-256 above, matching RFC 4231's known-answer tests.
A header and a payload of claims are base64url-encoded, joined with a dot, and signed with HMAC-SHA256.
The HMAC is recomputed and compared in constant time. No early-exit byte compare leaks timing.
Claims carry issued-at and expiry timestamps, checked after the signature, never before.
A salted, HMAC-based stretch, labeled honestly as demo-grade, not a substitute for Argon2.
Readability is the point. Every decision below is in service of code you can open and check, not a benchmark.
SHA-256 is written from the FIPS 180-4 spec and HMAC-SHA256 from RFC 2104. The only dependency is clap, for argument parsing. Nothing hides the cryptography behind a black box.
Signatures are compared with no early exit, so a rejected token leaks no timing signal about how many bytes matched.
Expiry and subject are read only after the HMAC verifies. A tampered token fails on the signature, before its claims are ever trusted.
Tokens, subjects, secrets, and passwords reject oversized input before any hashing, and every failure path returns a typed error rather than panicking.
Password hashing is a salted HMAC stretch, labeled demo-grade in the README. It is not Argon2, scrypt, or bcrypt, and it says so. Use Argon2id for real passwords.
gatekeeper issue, verify, hash, and check, built on clap.
A small Rust crate with typed errors, so a caller can match on exactly why a token failed.
The demo above is the same algorithm, ported to JavaScript and checked against the same RFC vectors on load.
cargo test runs SHA-256 and HMAC-SHA256 against known-answer vectors, plus round-trip, tamper, and expiry tests.
# issue a token, valid for one hour (real output from the release binary) $ gatekeeper issue --sub alice --ttl 3600 --secret my-signing-key eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZSIsImlhdCI6MTc4ODc4NTU1NCwiZXhwIjoxNzg4Nzg5MTU0fQ.jlqdRAZD8lVWz2QQJhACzB4lmitXe7-Z5YtfJOjNmj0 # verify it: recompute the HMAC, compare in constant time, then check expiry $ gatekeeper verify <token> --secret my-signing-key valid sub: alice iat: 1788785554 exp: 1788789154 # wrong secret is rejected before any claim is trusted $ gatekeeper verify <token> --secret wrong-key invalid: signature does not match, token rejected # salted, stretched password hash, then a check $ gatekeeper hash "correct horse battery staple" gkdf-hmac-sha256$100000$m28uNVPdvAViSu3ETh1uJA$A3I69UglOe5Yc7vPsvIWQwwNL3hJ3AxxUhE7nCl51YE $ gatekeeper check "correct horse battery staple" <hash> match