HMAC, Explained

After reading this you will know what an HMAC is, how the keyed-hash construction works, how to reproduce a signature by hand, and how to verify one safely without leaking your secret through a timing side channel.

What an HMAC is

HMAC stands for Hash-based Message Authentication Code. It is a fixed-size tag computed from two inputs: a message and a secret key. Anyone who holds the key can compute the tag; anyone who receives the message and tag can recompute it and check that the two match. If they match, the message has not changed and it came from someone who knew the key.

A plain hash like SHA-256 gives you integrity only. If an attacker changes the message, they can also recompute the hash, so the hash alone proves nothing about who sent it. HMAC folds a secret into the computation, so the tag proves both integrity and authenticity at once.

Here is the hook. GitHub signs its webhook payloads with HMAC-SHA256. When a push event arrives, the request carries a header like X-Hub-Signature-256: sha256=<hex>. You compute HMAC-SHA256 of the raw request body using your shared webhook secret, and if your hex matches the header, the payload is genuine. Stripe, Shopify and Slack all do the same thing with small variations.

When to use it, and when not to

Reach for HMAC when two parties already share a secret and you need to prove that a message was authored by one of them. Webhook signatures, API request signing (AWS Signature Version 4 uses HMAC-SHA256 internally), session cookie integrity, and JWT tokens signed with HS256 are all HMAC.

Do not use HMAC when the two parties do not share a secret. If a public client needs to verify a signature from a server without holding the server's private key, you need asymmetric signatures instead (RSA or ECDSA, the JWT RS256 and ES256 algorithms). HMAC verification requires the same secret used to sign, so publishing that secret for verification would let anyone forge tags.

An HMAC is not encryption. It does not hide the message. It only detects tampering and proves authorship. If the message must stay secret, encrypt it separately.

The formula and the intuition

RFC 2104 defines HMAC as a nested pair of hash calls:

\text{HMAC}(K, m) = H\big((K' \oplus opad) \,\|\, H((K' \oplus ipad) \,\|\, m)\big)

Reading the symbols: H is the underlying hash (SHA-256, SHA-512, SHA-1 or MD5). m is the message. K' is the key adjusted to the hash's block size. The symbol \oplus is bitwise XOR, and \| is concatenation. The two pads are fixed constants: ipad is the byte 0x36 repeated, and opad is the byte 0x5c repeated.

The key preparation step matters. The block size B is 64 bytes for SHA-256, SHA-1 and MD5, and 128 bytes for SHA-512. If the key is longer than B, it is first hashed down to the digest length. If it is shorter, it is padded on the right with zero bytes up to B. That padded value is K'.

Why two hash passes and two pads? The nesting defends against length-extension attacks. A single pass like H(K \,\|\, m) is forgeable against Merkle-Damgard hashes such as SHA-256: an attacker who knows H(K \,\|\, m) and the length of the key can append data and compute a valid tag for the extended message. The outer hash over a differently-padded key blocks that.

A worked example you can reproduce

HMAC-SHA256 of "Hello, World!"

Use the message Hello, World! with the key secret, algorithm SHA-256, output hex. These are the tool's defaults, so one click on Calculate reproduces the tag.

  1. The key secret is 6 bytes, shorter than the 64-byte block. Pad it with 58 zero bytes to get K' of length 64.
  2. XOR K' with ipad (0x36 repeated). Concatenate the message and hash with SHA-256. This is the inner digest, 32 bytes.
  3. XOR K' with opad (0x5c repeated). Concatenate the inner digest and hash again with SHA-256.
  4. Encode the final 32 bytes as hexadecimal.

The result is:

fcfaffa7fef86515c7beb6b62d779fa4ccf092f2e61c164376054271252821ff

Every byte here is determined by the key and message. Change one character of either input and roughly half the output bits flip. That is the avalanche property, and the next section shows it.

The avalanche effect in numbers

A good keyed hash spreads any input change across the whole output. Flip a single bit in the message and about 50% of the 256 output bits change, with no visible relationship to the input. The chart below shows the count of differing output bits when you toggle each of the first 24 message bits one at a time. The values cluster near 128, which is half of 256.

Each bar is near 128, meaning half the output changes regardless of which input bit moved. There is no pattern an attacker can exploit.

Because the change is unpredictable without the key, you cannot forge a tag for a modified message. You would have to guess a 256-bit value, which for SHA-256 means one chance in 2^{256}, about 1.16 \times 10^{77}.

Reading and interpreting the output

The digest length depends only on the hash, not the key or message length. HMAC-SHA256 is always 32 bytes, HMAC-SHA512 is 64 bytes, HMAC-SHA1 is 20 bytes, and HMAC-MD5 is 16 bytes.

Output sizes by algorithm
AlgorithmBlock size (bytes)Digest (bytes)Hex lengthBase64 length
HMAC-SHA25664326444
HMAC-SHA5121286412888
HMAC-SHA164204028
HMAC-MD564163224

Hex and Base64 encode the same bytes; they are two views of one value. A 32-byte digest is 64 hex characters or 44 Base64 characters (32 bytes rounds up to 44 Base64 characters including one = pad). If a webhook provider sends you Base64 but you computed hex, decode one side before comparing. The Base64 / URL / HTML Encoder-Decoder converts between the two, and the String Length Inspector tells you whether your key has the byte count you expect.

Changing any single character of the message or the key produces a completely different tag with no resemblance to the original. There is no partial match: either the whole 32-byte digest is identical or it is unrelated.

Common mistakes

The most frequent errors are not in the math (the library handles that) but in what you feed it and how you compare the result.

Signing the parsed body instead of the raw bytes
Webhook providers sign the exact bytes they sent. If your framework parses JSON and re-serializes it, key order and whitespace can change, and your HMAC will not match. Always sign the raw request body.
Encoding mismatch on the key
Some providers give the secret as a hex or Base64 string that must be decoded to bytes before use. Feeding the printable string directly gives a different K' and a wrong tag.
Comparing with ==
A normal string comparison returns as soon as two bytes differ. The time it takes leaks how many leading bytes matched, which lets an attacker recover a valid signature byte by byte. Use a constant-time comparison that always inspects the full length.
Trusting MD5 or SHA-1 for new work
HMAC-MD5 and HMAC-SHA1 are not broken by the collision attacks that break bare MD5 and SHA-1, but there is no reason to pick them for new systems. Use HMAC-SHA256 unless a protocol forces otherwise.

To verify, recompute the HMAC yourself over the received message and compare against the provided tag with a constant-time check. Never ask the sender to tell you whether your tag was "close".

Related tools

HMAC underpins the HS256 signature in a JSON Web Token: the signature is HMAC-SHA256 of the Base64url header and payload joined by a dot. Decode the parts with the JWT Decoder, then confirm the signature here with the token's secret. For plain unkeyed digests, use the Hash Generator. When you need to reason about digest byte counts versus encoded lengths, the Byte Size Converter keeps the units straight.

Frequently asked questions

Is HMAC the same as hashing with a salt?

No. A salt is a public, per-record value added to defeat precomputed tables when hashing passwords. An HMAC key is secret and shared, and its purpose is authentication, not password storage. Different jobs, different constructions.

Why does HMAC-SHA512 use a 128-byte block but the same formula?

The block size follows the underlying hash. SHA-512 processes 128-byte (1024-bit) blocks, so K' is padded to 128 bytes and the pads repeat to that length. The nested structure is identical.

Can two different keys produce the same tag for one message?

In principle yes, because the output space is finite, but the chance is astronomically small. For HMAC-SHA256 you would expect a collision only after roughly 2^{128} tries, about 3.4 \times 10^{38}.

Does the message length change the tag length?

No. A 3-byte message and a 3-megabyte message both produce a 32-byte HMAC-SHA256 tag. Only the hash choice sets the output size.

Is it safe to compute HMAC in the browser?

For learning, testing and verifying values it is fine, and this tool runs entirely on your device so nothing you paste is sent anywhere. For a production secret, keep the key on a server you control rather than shipping it to client code.