FlowingDev

HMAC, explained: the digital secret handshake

Learn how HMAC (Hash-based Message Authentication Code) verifies data integrity and authenticity, ensuring messages haven't been tampered with in transit.

Try the tool: HMAC Generator

In one sentence

HMAC is a cryptographic handshake that uses a shared secret key to prove a message is authentic and hasn't been messed with.

The problem it solves

Back in the early, wild-west days of the internet, sending a message was like sending a postcard. Anyone who intercepted it could read it, and maybe even scribble on it before sending it along. If you got a postcard that said "Meet me at midnight, bring the cash," how could you be sure it was really from your secret agent contact, and not from their nemesis, Evil Eve? And how could you be sure it didn't originally say "Meet me at noon for a friendly lunch"?

This is the dual problem of authenticity (is it really from you?) and integrity (has it been changed?).

A simple cryptographic hash function (like SHA-256) seems like a good first step. You could hash your message, send the message and the hash, and the receiver could re-hash the message to see if they match. Great! That solves integrity. If a single byte of the message was changed, the hashes won't match.

But it doesn't solve authenticity. Evil Eve can just change the message, calculate a new hash for her new message, and send both along. The receiver will see that the hash matches the message, but they have no way of knowing the whole package is a forgery.

This is where HMAC (Hash-based Message Authentication Code) rides in. By introducing a shared secret key into the hashing process, HMAC creates a signature that only someone with that secret key can produce. It’s the difference between a simple wax seal (anyone can make one) and a wax seal made with a unique signet ring (only the king has one). HMAC gives us both integrity and authenticity.

How it works under the hood

HMAC isn't a new type of hash function; it's a recipe that uses existing hash functions (like SHA-256) in a clever, specific way. The official spec is RFC 2104, but let's break it down into plain English.

The Ingredients

To cook up an HMAC, you need three things:

  1. The Message: The data you want to protect. This could be a JSON payload for a webhook, a string of URL parameters, or any chunk of binary data.
  2. The Secret Key: A string of bytes known only to the sender and the receiver. This is the magic ingredient. If it's compromised, the whole system is broken.
  3. The Hash Function: A standard algorithm like SHA-1, SHA-256, or SHA-512. The choice of hash function determines the length of the final HMAC signature (e.g., HMAC-SHA256 produces a 256-bit signature).

The Recipe (The HMAC Algorithm)

You might think you could just do hash(key + message). It seems simple, but this construction is vulnerable to some clever crypto shenanigans called "length extension attacks." The official HMAC construction is a bit more involved, specifically to prevent these attacks. It uses a double-hashing process.

Here's a simplified look at the steps:

  1. Prepare the Key: The hash function operates on fixed-size blocks of data (e.g., 64 bytes for SHA-256). The key needs to be prepared to fit this block size.

    • If the key is longer than the block size, you hash the key itself and use that result as the new key.
    • If the key is shorter than the block size, you pad it with zero bytes until it reaches the block size.
  2. Create Inner and Outer Keys: From this prepared key, we derive two separate keys.

    • ipad (inner pad): a constant byte (0x36) repeated to fill the block size.
    • opad (outer pad): a different constant byte (0x5C) repeated to fill the block size.

    We create an inner_padded_key by taking our prepared key and XORing it with ipad. We create an outer_padded_key by XORing the prepared key with opad.

  3. Perform the Double Hash: Now for the main event.

    • Inner Hash: Concatenate the inner_padded_key with the original message, and run it through the hash function.
    • Outer Hash: Concatenate the outer_padded_key with the result of the inner hash, and run that through the hash function.

The final result of this outer hash is your HMAC signature!

In pseudocode, it looks like this:

function hmac(key, message, hash_function, block_size) {

  // 1. Prepare the key
  if (key.length > block_size) {
    key = hash_function(key);
  }
  if (key.length < block_size) {
    key = pad_with_zeros(key, block_size);
  }

  // 2. Create inner and outer padded keys
  o_key_pad = key XOR (0x5C repeated to block_size);
  i_key_pad = key XOR (0x36 repeated to block_size);

  // 3. Perform the double hash
  inner_hash_result = hash_function(i_key_pad + message);
  final_hmac = hash_function(o_key_pad + inner_hash_result);

  return final_hmac;
}

Why the Double Hash?

This inner-then-outer structure is the secret sauce. The inner hash combines the secret and the message. The outer hash then essentially hashes the result of the first operation again with the secret. This "seals" the inner hash. It makes it computationally impossible for an attacker to manipulate the intermediate hash result without knowing the key, thus thwarting length extension attacks and other potential cryptographic breaks. It's a proven, robust construction that has stood the test of time.

Real-world stories

The GitHub Webhook Guardian

A startup team had their continuous integration (CI) server set up to automatically deploy their app to production every time someone pushed to the main branch. The trigger was a webhook from GitHub: a POST request sent from GitHub's servers to a public URL on their CI server. One night, their prankster intern found the public URL and, using a simple cURL command, started sending fake webhook payloads, triggering dozens of useless, resource-hogging deployments.

The senior dev fixed it in 15 minutes. In GitHub's webhook settings, she generated a long, random "secret." She copied this secret and configured it as an environment variable on their CI server. GitHub now used this secret to generate an HMAC-SHA256 signature for every webhook payload, sending it along in a X-Hub-Signature-256 header. The CI server's code was updated to perform the same HMAC calculation on the raw request body it received, using the same secret. If its calculated signature matched the one in the header, the request was processed. If not, it was rejected with a 403 Forbidden. The pranks stopped immediately.

Lesson: Always secure your webhooks with HMAC signature verification. Trust no incoming request until it's been authenticated.

Securing the API Cookie Jar

A developer was building a service that used a simple, signed cookie for authentication. When a user logged in, the server would issue a cookie containing their user_id and an expiry timestamp. To prevent users from editing their cookie to become a different user (e.g., changing user_id=123 to user_id=1), the developer included an HMAC signature.

The cookie payload looked something like this: user_id=123&expiry=1678886400. The server would sign this exact string with a secret key stored on the server. The final cookie sent to the browser was data="user_id=123&expiry=1678886400"&signature="sha1=2a8b...".

When the user made a subsequent request, their browser sent the cookie back. The server would take the data part, re-calculate the HMAC signature using its secret key, and compare it to the signature part of the cookie. If they matched, the server knew the user ID and expiry date were legitimate and hadn't been tampered with.

Lesson: HMAC is a fantastic way to create tamper-proof, "stateless" tokens or cookies, forming the basis for many authentication systems, including JSON Web Tokens (JWTs).

The Bank Transfer That Didn't Get Hijacked

An e-commerce platform integrated with a payment processor's API to initiate payouts to its vendors. The API call was a simple JSON message: {"vendor_id": "ven_abc", "amount": 500.00, "currency": "USD"}. The platform was worried about a man-in-the-middle (MITM) attack. Even over HTTPS, which encrypts the traffic, a sophisticated attacker could (in some theoretical scenarios, like with a compromised certificate authority) intercept and modify the request. They could change the amount to 50000.00 or the vendor_id to their own.

The payment processor's API required every request to be signed with HMAC-SHA512. The platform would serialize the JSON payload into a canonical string, calculate the signature with their private API key, and send it in an Authorization header. The payment processor's servers would perform the exact same steps. If their calculated signature matched the one sent in the header, they knew two things for sure: the request came from the legitimate platform (authenticity) and the vendor_id and amount hadn't been altered in transit (integrity).

Lesson: For high-stakes operations, HMAC provides a critical layer of security to guarantee the sender and content of a message are what you expect.

Common mistakes and traps

  • Leaking the secret key. The key is everything. If you expose it in client-side JavaScript, commit it to a public Git repository, or log it in plain text, your security is gone. Treat it like a password.
  • Using a non-constant-time comparison. When you check if the user-provided signature matches your calculated one, a standard string comparison like if (a === b) can be a security hole. It often returns false as soon as it finds a mismatched character. This creates a tiny time difference that attackers can measure to guess the signature one character at a time. This is a "timing attack." Always use a dedicated, "constant-time" comparison function from a crypto library that takes the same amount of time regardless of where the mismatch occurs.
  • Signing the wrong data. The sender and receiver must calculate the HMAC over the exact same sequence of bytes. A common bug is for one side to sign a prettified JSON object while the other signs the compact, one-line version. Or one side includes a trailing newline and the other doesn't. You must agree on a canonical message format and stick to it.
  • Forgetting about replay attacks. HMAC itself doesn't stop an attacker from capturing a valid, signed message and just re-sending it over and over. If that message is "pay Bob $10," you don't want the attacker to be able to trigger that payment 1000 times. To prevent this, include a value that changes with each request—like a timestamp or a "nonce" (number used once)—inside the data being signed. The server can then check the timestamp to reject old requests or keep a list of used nonces to reject duplicates.

Why it belongs on your radar

You should think of HMAC whenever you're dealing with communication that needs to be trusted. It's not about keeping the data secret (that's encryption's job), but about ensuring the data is legitimate.

  • Building or consuming APIs? Especially with webhooks (from services like Stripe, GitHub, Twilio), HMAC is the industry standard for verifying that the request is authentic.
  • Working with authentication? Many token-based systems, most famously JWTs, use HMAC (e.g., the 'HS256' algorithm) to sign the token's payload, preventing users from modifying their own permissions.
  • Need to verify data integrity? If you're passing data through an untrusted environment (like a user's browser in a cookie) and need to make sure it comes back unmodified, HMAC is your tool.

It’s a fundamental primitive in a web developer's security toolbox. Understanding how it works will make you a better, more security-conscious engineer.

Go deeper

Theory done. Time to get your hands dirty — 100% in your browser.

Try the tool: HMAC Generator