HMAC Generator
28 bytes of UTF-8. A signature is over bytes, so a re-indented JSON body or a CRLF where the sender used LF gives a different tag.
4 bytes — short for a shared secret; 32 bytes of randomness is the usual floor
HMAC-SHA256 tag 256 bits
HMAC-SHA256 over 28 bytes with a text (utf-8) key — 64 hex characters ending —.
Does it match?
Nothing pasted to compare against. Put the signature header the sender sent in the box above and this says whether it matches the tag computed here.
How this was worked out
- Key read asText (UTF-8) — 4 bytes
- Message read asUTF-8 — 28 bytes, exactly as they would arrive on the wire
- Hash usedHMAC-SHA256, a 64-byte block and a 256-bit tag
- ConstructionH((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ message)) — RFC 2104, run by crypto.subtle rather than by anything in this page
- Tag written as64 hex characters, or 44 of base64
Comparing the two strings with === is the bug this tool cannot show you. On this page the comparison is harmless, because both values are already in front of you. In a webhook handler it is not: a comparison that stops at the first wrong character takes measurably longer the more of the prefix an attacker guessed right, which is enough to recover a valid tag one character at a time. Servers need a constant-time compare —crypto.timingSafeEqual in Node, hmac.compare_digest in Python.
An HMAC proves that whoever produced the tag held the key, and nothing else. It does not say when the message was sent, so a replay of a genuine signed request is still genuine: that is why providers put a timestamp in the signed material and refuse anything older than a few minutes. It also gives no non-repudiation, because both sides hold the same secret and either could have produced any tag — a digital signature is what you want when that matters.
Everything on this page stays in this page: the key, the message and the tag are computed by crypto.subtle on your machine, no request is made, and none of the three is put in the address bar — a shared link carries the hash and the key encoding and nothing else. That is still not a reason to paste a production signing secret into a web page.
Why a webhook arrives with a signature attached
A digest tells you that two piles of bytes are identical. It cannot tell you who produced them, because anyone can hash anything. HMAC closes that gap by folding a shared key into the hashing itself, in the two-pass construction RFC 2104 specifies: the receiver recomputes the tag from the body it was handed and its own copy of the key, and a value that matches could only have come from somebody holding that key.
That is what the header on an incoming webhook is for. Stripe, GitHub, Shopify and Slack all sign their request bodies this way, and the instruction in every one of their integration guides is the same — recompute the tag before you read a single field of the payload, because an unsigned request is just a POST from a stranger.
Hashing exactly the bytes the sender hashed
Almost every failed comparison comes down to signing something subtly different from what went over the wire. The sender signed a raw body; a framework that parses JSON and re-serialises it changes the spacing, the key order or the Unicode escaping, and the tag moves. Copying a payload out of a log viewer can convert line endings from LF to CRLF, which is another set of bytes again.
The key has the same problem in a smaller space. A secret printed as 64 hexadecimal characters is 32 bytes, not 64, and treating those characters as text produces a perfectly well-formed tag for a key nobody else has. This tool asks how the key is written for exactly that reason, and reports how many bytes it decoded to so the mistake is visible before the comparison fails.
What a matching tag does not establish
Authenticity is not freshness. A signed request captured once stays valid forever unless something else expires it, which is why providers sign a timestamp alongside the body and reject anything more than a few minutes old — a check the signature cannot perform for you.
Nor is it proof of who sent it, in the sense a court would want. Both ends hold the same secret, so either could have produced any tag, and neither can prove the other did. That property is called non-repudiation and only asymmetric signatures have it. When more than one party needs a key, remember that every additional holder is another place the whole scheme can leak from.
The comparison itself is part of the check
Once the tag is computed, how it is compared matters. A plain string equality returns as soon as it finds a difference, so a wrong guess that shares the first four characters takes measurably longer to reject than one that shares none. Repeated a few thousand times against a live endpoint, that timing difference is enough to reconstruct a valid tag character by character without ever knowing the key.
The fix is a comparison whose duration depends only on length: timingSafeEqual in Node, hmac.compare_digest in Python, hash_equals in PHP. On this page it does not matter, because both values are already on your screen — but the habit belongs in the handler you are debugging.
When the tag never matches
My recomputed tag never matches the header. Where do I start?
Check the key encoding first, then the body. Capture the raw request bytes before any middleware touches them, confirm the hash the provider documents, and try the secret as text and as decoded hex. One of those four is nearly always the culprit.
Is HMAC-SHA1 broken the way SHA-1 is?
Not in the same way. HMAC keeps its security even when its underlying hash has collisions, so existing HMAC-SHA1 deployments are not urgently unsafe. Nothing new should choose it, though, and this tool omits it so it cannot be picked by accident.
Does a very long secret make the tag stronger?
Only up to the hash block size — 64 bytes for SHA-256, 128 for SHA-512. RFC 2104 says a longer key is hashed down first, so a 200-character secret is silently reduced. Thirty-two bytes from a real random source is the sensible target.
Can I use this page to sign something I then rely on?
For checking what your own code should produce, yes. For anything live, no: a secret pasted into any web page has been outside the systems that were meant to hold it, and the right response to that is rotating the key rather than hoping.
Why not simply hash the key and the message together?
Because that construction is vulnerable to length extension on the SHA-2 family: an attacker holding one tag can append data and produce a valid tag for the longer message without ever knowing the key. HMAC’s two passes, with two keys derived from yours, are what close that.