In revision.
Crisp5 min readGo deeper →

HMAC signatures

HMAC is a symmetric MAC built on a hash function. Both sides share a secret, both compute the same tag, attacker without the secret cannot forge.

HMAC is the answer to "how do I prove this message came from someone who knows the secret, and was not tampered with." It is symmetric (both sides hold the same key), it is fast (microseconds), and it is everywhere (webhooks, JWT HS256, AWS SigV4, session cookies).

The construction is HMAC(K, m) = H((K xor opad) || H((K xor ipad) || m)) where H is a hash function. You do not need to remember the formula. What matters is: it takes a key and a message, returns a fixed-size tag (32 bytes for HMAC-SHA256), and an attacker without the key cannot produce a valid tag for a new message.

HMAC flow: both sides recompute, both sides compare in constant time.

Use HMAC when both parties share a secret. Use a digital signature (RSA, ECDSA, Ed25519) when only the signer should be able to produce signatures. The decision tree is one question: can the verifier be trusted with the key to forge messages? If yes, HMAC is fine and faster. If no, you need asymmetric signatures.

The canonical use case is webhook verification. Stripe signs every webhook with HMAC-SHA256(your_webhook_secret, timestamp + "." + body). Your server recomputes the same HMAC and compares. If they match, the webhook is genuine. If not, drop it.

Three rules I never break.

  • Use crypto.timingSafeEqual (Node) or hmac.compare_digest (Python) for comparison. A plain == returns false on the first byte mismatch, leaking the position of the wrong byte through timing.
  • Include a timestamp in what you sign and reject anything older than a few minutes. Stops replay attacks.
  • The secret must be at least as long as the hash output. For HMAC-SHA256, use 32 random bytes (openssl rand -hex 32).

When an interviewer asks "how would you verify a webhook" the right answer is: shared secret, HMAC-SHA256 over timestamp + body, constant-time compare, reject if timestamp is older than five minutes. That is the whole spec.

Learn more