Gatekeeper logo

Rust · no crypto crates · known-answer tested

An auth server in Rust
you can read end to end.

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.

Try it live View on GitHub
Issue or paste a token, then verify.

running SHA-256 / HMAC-SHA256 self-check against RFC 4231 vectors…

How to use this playground
Set a Subject, Secret, and TTL, then hit Issue token to mint an HMAC-SHA256 signed token and see its decoded header and payload. Copy it into Token to verify (issuing fills it in automatically) and press Verify to recompute the signature and check expiry. Hit Tamper & verify to flip one byte of the payload and watch the signature check reject it. The line at the bottom shows the SHA-256 and HMAC self-check against the RFC 4231 vectors, the same algorithm as the Rust crate, ported to JavaScript.

How it works

Every step below is code you can read, no dependency does the cryptography for you.

1

SHA-256

The compression function, message schedule, and padding are written from the FIPS 180-4 spec, no crates.

2

HMAC-SHA256

Key padding, inner and outer hash, built directly on the SHA-256 above, matching RFC 4231's known-answer tests.

3

Sign

A header and a payload of claims are base64url-encoded, joined with a dot, and signed with HMAC-SHA256.

4

Verify

The HMAC is recomputed and compared in constant time. No early-exit byte compare leaks timing.

5

Expire

Claims carry issued-at and expiry timestamps, checked after the signature, never before.

6

Hash passwords

A salted, HMAC-based stretch, labeled honestly as demo-grade, not a substitute for Argon2.

Design choices, not defaults.

Readability is the point. Every decision below is in service of code you can open and check, not a benchmark.

From scratch, no crypto crates

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.

Constant-time comparison

Signatures are compared with no early exit, so a rejected token leaks no timing signal about how many bytes matched.

Signature before claims

Expiry and subject are read only after the HMAC verifies. A tampered token fails on the signature, before its claims are ever trusted.

Bounded input, no panics

Tokens, subjects, secrets, and passwords reject oversized input before any hashing, and every failure path returns a typed error rather than panicking.

Honest about the password hash

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.

Same engine, four surfaces.

CLI

gatekeeper issue, verify, hash, and check, built on clap.

Library

A small Rust crate with typed errors, so a caller can match on exactly why a token failed.

Browser

The demo above is the same algorithm, ported to JavaScript and checked against the same RFC vectors on load.

Tests

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