How to use
- Choose the algorithm. HMAC-SHA256 is selected by default and is what most webhooks and APIs use.
- Enter the secret key and set Key format to Text (read as UTF-8), Hex or Base64. Use Hex or Base64 when the key is raw bytes rather than a password-like string.
- Type or paste the message, switch the input to Hex for raw bytes, or open a file. The HMAC updates as you type.
- Choose lowercase hex, uppercase hex or Base64 output and copy the result, or paste a received signature into Compare with to see Match or No match.
How HMAC works
HMAC (hash-based message authentication code, RFC 2104) combines a cryptographic hash with a secret key. Only someone who knows the key can produce the right value for a message, so a matching HMAC proves two things at once: the message was not changed, and it came from a holder of the key.
The construction is H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ message)). The key is hashed first if it is longer than the hash's block size (64 bytes for MD5, SHA-1 and SHA-256, 128 for SHA-384 and SHA-512), padded with zeros to that size, then XORed with ipad (0x36 bytes) for an inner hash over the message and with opad (0x5c bytes) for an outer hash over the inner result. A naive SHA-256(key ‖ message) has no outer hash, so anyone who sees one tag can append data to the message and compute a valid new tag without the key (a length-extension attack).
The output has the length of the underlying hash: 32 bytes, or 64 hex characters, for HMAC-SHA256. RFC 2104 recommends a key at least that long, and the random Base64 generator makes suitable ones.
Where HMAC signatures are used
- GitHub webhooks. The
X-Hub-Signature-256header issha256=followed by the hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. You can paste the whole header value into Compare with; thesha256=prefix is ignored. - Stripe webhooks. The
Stripe-Signatureheader carries a timestamptand a signaturev1: the hex HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with the endpoint secret (whsec_...) used as text. - Shopify webhooks.
X-Shopify-Hmac-Sha256holds the HMAC-SHA256 of the raw body in Base64, keyed with the app's client secret, so choose Base64 output or paste the header value into Compare with. - AWS Signature Version 4. The signing key is derived by chaining HMAC-SHA256: the date keyed with
AWS4plus the secret access key, then the region, the service andaws4_request, each keyed with the previous result. Set the key format to Hex to feed one step's output into the next. - JSON Web Tokens. HS256, HS384 and HS512 are HMAC-SHA256, -384 and -512 over the encoded header, a dot and the encoded payload. The third part of the token is that HMAC in URL-safe Base64, which Compare with accepts as it is. The JWT decoder verifies whole tokens.
Always compute the HMAC over the raw body exactly as it was received. Parsing JSON and serializing it again changes spacing or key order, and even one extra newline gives a completely different HMAC.
HMAC in code
# Python
import hmac, hashlib
sig = hmac.new(b'key', b'message', hashlib.sha256).hexdigest()
hmac.compare_digest(sig, received) # constant-time comparison
// Node.js
const crypto = require('crypto');
const sig = crypto.createHmac('sha256', 'key').update('message').digest('hex');
// timingSafeEqual throws if the lengths differ, so check them first
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(received));
// PHP: the message comes before the key
$sig = hash_hmac('sha256', 'message', 'key');
hash_equals($sig, $received);
# OpenSSL: a text key, then the same key given as hex
printf '%s' 'message' | openssl dgst -sha256 -hmac 'key'
printf '%s' 'message' | openssl dgst -sha256 -mac HMAC -macopt hexkey:6b6579
# Test vector: key "key", message "The quick brown fox jumps over the lazy dog"
# HMAC-SHA256 = f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8
When you verify a signature in your own code, use a constant-time comparison like the ones above. A plain == stops at the first differing character, and that timing difference can let an attacker find a valid signature byte by byte. For timestamped schemes such as Stripe's, also reject requests whose timestamp is more than a few minutes old, which blocks replays.
Frequently asked questions
Is HMAC-MD5 or HMAC-SHA1 still secure?
Collision attacks break MD5 and SHA-1 as plain hashes, but they do not give a practical way to forge an HMAC, which depends on other properties of the hash and on a secret key (RFC 6151 discusses HMAC-MD5). Integrations that require them, such as TOTP codes based on HMAC-SHA1, are fine to keep. For new designs, use HMAC-SHA256.
Why does my HMAC not match the webhook signature?
The usual causes are hashing parsed and re-serialized JSON instead of the raw body, a trailing newline in the message, the wrong secret or key format (a Base64 or hex secret entered as Text, or the reverse), and the wrong algorithm. Hex versus Base64 is not a problem here: Compare with accepts hex in either case, standard Base64 and URL-safe Base64.
What is the difference between HMAC and a plain hash?
Is an HMAC the same as a digital signature?
No. Both sides of an HMAC share one secret, so anyone who can verify a tag can also create one. Signatures such as RSA or ECDSA (RS256 or ES256 in JWTs) are made with a private key and checked with a public key, so the people who verify them cannot forge them.
Is my secret key sent to a server?
No. HMAC-SHA1 and the SHA-2 variants are computed with your browser's Web Crypto API, and HMAC-MD5 by JavaScript on the page. The key and the message never leave your device. Even so, handle production secrets with care and rotate a key if it may have been exposed.