What is HMAC?
HMAC (Hash-based Message Authentication Code, RFC 2104) combines a cryptographic hash function with a shared secret key to produce a fixed-length tag that authenticates a message. It guarantees both integrity (the message was not modified) and authenticity (it was signed by someone holding the secret). HMAC is the workhorse behind webhook signatures, JWT HS256 tokens, AWS SigV4 request signing, and millions of REST API authentication flows.
HMAC vs plain SHA-256 — what is the difference?
A bare SHA-256 of (key + message) is vulnerable to length-extension attacks: an attacker who knows H(key||msg) can compute H(key||msg||extra) without the key. HMAC defeats this with two nested hashes using inner and outer key pads (ipad/opad). Always use HMAC — never raw SHA-256(secret + payload) — when you need a keyed authentication tag.
Which algorithm should I pick?
HMAC-SHA256 is the default for new work — it matches GitHub webhooks, Stripe webhooks, AWS SigV4, and JWT HS256. HMAC-SHA512 gives extra collision margin and is faster than SHA-256 on 64-bit CPUs for long messages. Use HMAC-SHA1 only when interoperating with older systems (Twilio request validation, legacy AWS S3 signatures); SHA-1 collisions do not break HMAC-SHA1, but new code should still pick SHA-256.
How long should the secret key be?
RFC 2104 recommends a key at least as long as the hash output: 32 bytes for HMAC-SHA256, 64 bytes for HMAC-SHA512. Keys longer than the hash block (64 bytes for SHA-1/256, 128 for SHA-384/512) are pre-hashed; shorter keys are zero-padded — both work, but a key shorter than the digest reduces effective security. Generate keys from a CSPRNG (crypto.getRandomValues) and store them in a secrets manager, never in source.
Hex or Base64 — which output format?
Hex is the convention for X-Hub-Signature-256 (GitHub webhooks), Stripe-Signature, and most webhook headers. Base64 is shorter and used by JWT HS256 (Base64URL specifically), AWS SigV4 headers, and HTTP Basic-style schemes. Hex is twice as long as Base64 for the same digest but is case-insensitive and easier to compare visually.
Is the secret sent to your servers?
No. HMAC computation runs entirely in your browser using the Web Crypto SubtleCrypto API. Open DevTools → Network and click Sign — no requests are made. The secret never touches our servers, our logs, or any third-party analytics.
Can I generate the same HMAC in Node, Python, and curl?
Yes — HMAC is a deterministic standard. Node: crypto.createHmac("sha256", secret).update(msg).digest("hex"). Python: hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest(). OpenSSL CLI: echo -n "msg" | openssl dgst -sha256 -hmac "secret". All three produce the same hex string this tool produces — handy for verifying server code matches your test vector.
What about HMAC for JWT tokens?
JWT HS256 is HMAC-SHA256 over the Base64URL-encoded header and payload joined by a dot, then the result Base64URL-encoded (no padding). To sign a JWT here, set message to "header.payload" (each part already Base64URL-encoded), pick HMAC-SHA256, choose Base64 output, and convert "+" to "-", "/" to "_", and strip "=" padding to get Base64URL. For end-to-end JWT work, use a dedicated library that handles canonical encoding.