Skip to content

HMAC generator

Sign a message with a secret key using HMAC-SHA256 and friends, without the key leaving your browser.

Runs in your browser — nothing is sent anywhere.

An HMAC is not a hash of the message and the key stuck together

That is the tempting implementation and it is broken. Naively hashing key + message is vulnerable to a length-extension attack: with most hash constructions an attacker who knows the digest and the message length can append data and produce a valid digest for the extended message, without ever knowing the key.

HMAC exists to close that. It hashes twice, with the key mixed in differently each time — an inner pass and an outer pass with different padding — and that structure is what makes it safe. This uses the browser's own WebCrypto implementation, which is the same code path the browser uses for TLS, rather than anything written here.

Where you actually meet one

Webhook verification is the common case. Stripe, GitHub, Shopify and most others sign their payloads with a shared secret, and your endpoint recomputes the HMAC over the raw body to prove the request came from them. Debugging that is miserable without a way to compute the expected value by hand, which is what this is for.

Two things go wrong every time. The body must be hashed exactly as received — parse it into an object and re-serialise it, and the whitespace changes and the signature stops matching. And the comparison must be constant-time in your own code: comparing signatures with == leaks timing information, and every provider's documentation says so for a reason.

Key encoding matters more than people expect

A key that arrives as hex is bytes, not text. Hashing the twelve characters "48656c6c6f21" is not the same as hashing the six bytes they represent, and it produces a completely different signature. That is the usual cause of "my HMAC does not match the documentation" — so the encoding is a field here rather than an assumption, covering plain text, hex and base64.

Hex or base64 output

Both are in use and neither is more correct. Stripe and GitHub publish hex; AWS Signature v4 uses hex; several others use base64. They are the same bytes shown two ways, and comparing one against the other is another way to spend an afternoon on nothing.

Your key does not leave the browser

This is the part that matters. A signing secret pasted into an online tool that posts to a server is a signing secret that now exists in somebody's request log. This runs in the page — there is no network request, and you can confirm that in your browser's network tab.

Even so: if you paste a production secret anywhere, rotate it. The tool is safe; the habit is not.

01

Common questions

Why does my HMAC not match what the provider sends?

Nearly always one of three things: the key encoding (hex or base64 keys are bytes, not text), a re-serialised request body instead of the raw bytes, or comparing hex against base64.

Which algorithm should I use?

SHA-256 unless the other side specifies otherwise. HMAC-SHA1 is still considered secure as a MAC and is used by some older APIs, but there is no reason to choose it for something new.

Is HMAC-SHA1 safe given SHA-1 is broken?

SHA-1 is broken for collision resistance, which HMAC does not depend on. HMAC-SHA1 is not considered broken — but do not pick it for a new design.

Can I use this to verify a webhook?

Yes. Paste the raw request body and the signing secret and compare the result with the signature header. Hash the body exactly as received, not a re-serialised version.

Does my secret key get uploaded?

No. Everything happens in your browser using WebCrypto. There is no network request at all.

What is the difference between a hash and an HMAC?

A hash needs no key and anyone can compute it. An HMAC needs the secret, so it proves the message came from someone who has it.