Cryptography

HMAC vs Hash: The Difference That Makes HMAC Secure

8 min read
By
HMAC vs Hash: The Difference That Makes HMAC Secure

Photo by Pixabay from Pexels

If you have ever verified a webhook signature, signed a JWT, or looked at how AWS signs API requests, you have used HMAC. But if you are like many developers, you might not have a clear mental model of what makes HMAC different from a plain hash. The distinction is small, but it is the difference between "this data was not corrupted" and "this data was not tampered with." Those are very different security guarantees.

Let's break down what a hash does, what HMAC adds, and why the difference matters in practice.

What a Plain Hash Does

A cryptographic hash function like SHA-256 takes any input and produces a fixed-size output. The same input always produces the same output. Different inputs produce (with overwhelming probability) different outputs. And you cannot reverse the output to find the input.

Hashes are great for verifying integrity. If you download a file and hash it, and the hash matches the one published by the author, you know the file was not corrupted in transit. A single bit flip changes the entire hash output.

But here is the problem: a hash does not require a secret. Anyone can compute the hash of any message. If an attacker intercepts a message, modifies it, and recomputes the hash, the recipient has no way to know the hash was recomputed by the attacker. The hash matches the modified message perfectly.

A hash protects against accidental corruption. It does not protect against intentional tampering.

What HMAC Adds

HMAC (Hash-based Message Authentication Code) solves this by adding a secret key to the hash computation. The formula is:

HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m))

Where K is the secret key, m is the message, H is the hash function, K' is the key padded to the block size, and ipad/opad are fixed padding constants. The details of the construction matter for security, but the key insight is simple: the output depends on both the message and the secret key.

This means only someone who knows the key can produce a valid HMAC for a given message. And only someone who knows the key can verify one. If an attacker modifies the message, they cannot compute the correct HMAC for the modified message because they do not have the key.

This is the difference between integrity and authenticity. A hash says "this message was not changed since it was hashed." An HMAC says "this message was not changed since it was HMAC'd, and the person who HMAC'd it had the secret key."

Why You Cannot Just Hash the Key Into the Message

A common mistake is to think you can build your own HMAC by just concatenating the key and the message and hashing them: H(K || m). This seems like it would work. It does not.

The problem is length extension attacks. Many hash functions, including SHA-256 and SHA-512, are based on the Merkle-Damgard construction. If you know H(K || m), you can compute H(K || m || extension) without knowing K, because the hash of the original message becomes the internal state for the extension. This means an attacker who sees H(K || m) can produce a valid hash for a longer message that includes their appended data.

HMAC's construction specifically prevents this. The inner and outer hash passes with the ipad and opad constants ensure that length extension does not work. The inner hash processes the key and message together, and the outer hash processes the key and the inner hash result. An attacker cannot extend the message because the outer hash wraps the inner hash output in a way that depends on the key.

This is why you should never build your own keyed hash. Use the standard HMAC construction that your language's crypto library provides. It has been carefully designed to avoid subtle attacks that naive constructions fall victim to.

Where HMAC Is Used in the Real World

HMAC is everywhere in modern systems. Here are the most common places you will encounter it:

**Webhook signatures.** When Stripe, GitHub, or Slack sends a webhook to your server, they include an HMAC of the request body. You verify the HMAC using your shared secret (the webhook signing key) before processing the request. This proves the webhook came from the service and was not modified in transit. We cover this in detail in our guide on verifying Stripe webhook signatures.

**JWT signing.** JSON Web Tokens can be signed with HMAC (the HS256 algorithm) or with RSA/ECDSA (RS256, ES256). HMAC-signed JWTs use a shared secret between the issuer and the verifier. For a comparison of the two approaches, see our breakdown of JWT HS256 vs RS256.

**AWS API signing.** AWS uses HMAC extensively in its Signature Version 4 process to sign API requests. Your AWS secret key is used to compute a chain of HMACs that ultimately produces a signature for each request. This proves the request came from someone with your secret key. See our AWS SigV4 explained post for the full walkthrough.

**API request signing.** Many REST APIs use HMAC to authenticate requests. The client computes an HMAC of the request method, path, timestamp, and body using a shared API secret. The server recomputes it and compares. This is simpler than OAuth for server-to-server communication.

The Security Properties HMAC Provides

HMAC gives you three guarantees, assuming the key remains secret:

**Integrity.** If the message is modified, the HMAC will not match. The probability of a modified message producing the same HMAC is negligible (approximately 2^-256 for HMAC-SHA256).

**Authenticity.** Only someone with the secret key can produce a valid HMAC. If the HMAC matches, the message came from someone who had the key.

**Replay resistance (with additional measures).** HMAC itself does not prevent replay attacks. An attacker can capture a valid message and HMAC and resend them later. But if the message includes a timestamp, nonce, or sequence number, the recipient can detect and reject replays.

What HMAC does not give you is non-repudiation. Because both parties share the same key, either party could have produced the HMAC. If you need to prove to a third party that a specific party signed a message, you need asymmetric signatures like RSA or ECDSA, not HMAC.

Choosing the Hash Function Inside HMAC

HMAC can be used with any secure hash function. The most common choices are HMAC-SHA256 and HMAC-SHA512. Both are secure. The choice usually comes down to performance characteristics on your platform and the size of the output you want.

Avoid HMAC-MD5 and HMAC-SHA1. While HMAC is more resistant to attacks on the underlying hash function than naive constructions, there is no reason to use weaker hash functions when SHA-256 and SHA-512 are just as fast and have no known weaknesses. For a detailed comparison, read our guide on HMAC-SHA256 vs SHA-512.

A Practical Example

Let's say you are building an API that receives webhook callbacks from a payment processor. The processor sends you the request body and an HMAC-SHA256 signature computed with your shared webhook secret.

Your verification code looks something like this:

const expectedHmac = hmacSha256(webhookSecret, requestBody); const isValid = timingSafeEqual(expectedHmac, receivedSignature);

That second line matters more than it looks. You must compare HMACs using a constant-time comparison function, not a regular equality check. Regular comparison short-circuits on the first differing byte, which leaks information about the HMAC through timing. An attacker can use this to forge signatures one byte at a time. We cover this in our guide on constant-time comparison.

The Bottom Line

A hash answers the question "was this data changed?" An HMAC answers the question "was this data changed by someone without the secret key?" That second question is almost always the one you actually need to answer in security systems.

If you are verifying that data came from a trusted source and was not modified in transit, you need HMAC, not a plain hash. The secret key is what transforms integrity into authenticity, and authenticity is what makes HMAC secure.

Want to try computing HMACs yourself? Use our HMAC generator to compute HMAC-SHA256 and HMAC-SHA512 with different keys and messages, and see the output in real time.

Frequently Asked Questions

What is the main difference between a hash and an HMAC?

A hash takes only the message as input and anyone can compute it. An HMAC takes both the message and a secret key as input, so only someone with the key can compute or verify it. A hash verifies data integrity (it was not corrupted). An HMAC verifies both integrity and authenticity (it was not tampered with by someone without the key).

Can I use a plain SHA-256 hash to verify webhook signatures?

No. A plain SHA-256 hash of the request body can be computed by anyone who sees the body. An attacker could modify the body, recompute the hash, and replace both. HMAC requires a secret key that the attacker does not have, so they cannot produce a valid signature for a modified message.

Is HMAC the same as encryption?

No. HMAC does not encrypt or hide the message. The message is sent in plaintext alongside the HMAC. HMAC only provides integrity and authentication, proving the message was not modified and came from someone holding the secret key. If you need confidentiality, you need encryption in addition to HMAC.

Which hash algorithm should I use inside HMAC?

SHA-256 is the most common and widely recommended choice for HMAC. SHA-512 is also excellent, particularly on 64-bit systems. Avoid MD5 and SHA-1 inside HMAC, not because HMAC is broken with them, but because SHA-256 and SHA-512 are strictly better and equally fast. For a detailed comparison, see our guide on HMAC-SHA256 vs SHA-512.

Try NovelCrypt Tools

Experience military-grade encryption for your sensitive data. Create self-destructing messages, encrypt files, or explore our experimental lab tools.

Explore NovelCrypt