When you need to verify that a message hasn't been tampered with AND came from a trusted source, plain hashing isn't enough. That's where HMAC comes in.
The Problem with Plain Hashes
Suppose you send a message with its hash:
Message: {"amount": 100, "recipient": "Alice"}
Hash: sha256(message) = a1b2c3...
An attacker could:
- Intercept the message
- Modify it to
{"amount": 10000, "recipient": "Mallory"} - Compute the new hash
- Send both to the recipient
The hash verifies integrity but not authenticity—anyone can compute it. A plain SHA-256 hash proves the data matches a digest, but it can't prove who produced that digest.
What is HMAC?
HMAC stands for keyed Hash-based Message Authentication Code (the "H" is for hash, and the key is what makes it keyed). It combines a cryptographic hash function with a secret key. Only parties who know the key can create or verify the HMAC.
HMAC(key, message) = hash(key XOR opad || hash(key XOR ipad || message))
Or more simply:
HMAC = Hash(Secret Key + Message) (with proper padding)
The secret key makes all the difference:
Message: {"amount": 100, "recipient": "Alice"}
Secret Key: "my-secret-key"
HMAC: HMAC-SHA256(key, message) = f4e5d6...
Without the key, an attacker can't forge a valid HMAC. The output is called a tag or signature, and it travels alongside the message.
How HMAC Works Step by Step
HMAC is a two-pass construction. If you were drawing it as a diagram, you'd see the key and message flow through the hash function twice—an inner pass and an outer pass.
Step 1 — Normalize the key to block size. Every hash function processes data in fixed-size blocks (SHA-1 and SHA-256 use a 64-byte block; SHA-512 uses 128 bytes). If the key is longer than the block size, it's hashed down first. If it's shorter, it's padded with zeros to fill exactly one block.
Step 2 — Derive two padded keys. HMAC XORs the block-sized key with two constants:
ipad = 0x36 repeated (block size times) → inner padding
opad = 0x5c repeated (block size times) → outer padding
XOR-ing the key against these two different byte patterns produces two distinct key variants, which is why HMAC resists attacks that plain hash(key + message) does not.
Step 3 — Inner hash. Prepend the ipad-keyed block to the message and hash it:
inner_hash = H((key XOR ipad) || message)
Step 4 — Outer hash. Prepend the opad-keyed block to the inner result and hash again:
HMAC = H((key XOR opad) || inner_hash)
The final output is the HMAC tag. Reading the diagram top to bottom: message + inner key → hash → intermediate digest + outer key → hash → tag.
This construction:
- Prevents length-extension attacks that affect naive
hash(key + message)schemes with Merkle–Damgård hashes like SHA-256 - Provides provable security: HMAC is secure as long as the underlying hash function is a reasonable pseudorandom function
- Works with any hash without needing a redesign per algorithm
MAC vs HMAC
People often use "MAC" and "HMAC" interchangeably, but they aren't the same thing. A MAC (Message Authentication Code) is the general category: any algorithm that produces an authentication tag from a message and a secret key. HMAC is one specific way to build a MAC—the one that uses a hash function.
| MAC | HMAC | |
|---|---|---|
| What it is | Any keyed authentication code | A specific hash-based MAC |
| Building block | Hash, block cipher, or other primitive | A cryptographic hash (SHA-256, etc.) |
| Examples | CMAC, GMAC, Poly1305, HMAC | HMAC-SHA256, HMAC-SHA1, HMAC-SHA512 |
| Standard | Umbrella concept | RFC 2104 / FIPS 198-1 |
So every HMAC is a MAC, but not every MAC is an HMAC. When someone says "use a MAC to sign the request," HMAC is usually the practical answer because hash functions are fast, well-vetted, and available in every standard library.
HMAC vs Hash Comparison
| Property | Plain Hash | HMAC |
|---|---|---|
| Input | Message only | Key + Message |
| Verifies integrity | ✓ | ✓ |
| Verifies authenticity | ✗ | ✓ |
| Anyone can compute | ✓ | ✗ (need key) |
| Prevents tampering | ✗ | ✓ |
HMAC-SHA256 vs HMAC-SHA1 vs HMAC-SHA512
HMAC is a wrapper—you plug a hash function into it. That choice of hash is where the naming comes from. HMAC-SHA256 means HMAC built on SHA-256, and it's the one you'll reach for most often.
| Algorithm | Output Size | Block Size | Use Case |
|---|---|---|---|
| HMAC-MD5 | 128 bits | 64 bytes | Legacy only |
| HMAC-SHA1 | 160 bits | 64 bytes | Legacy, some existing webhooks |
| HMAC-SHA256 | 256 bits | 64 bytes | Recommended default |
| HMAC-SHA512 | 512 bits | 128 bytes | Extra margin, or faster on 64-bit CPUs |
Which should you use?
- HMAC-SHA256 — the default for new work. It's fast, universally supported, and 256-bit tags are far beyond any brute-force reach. If you're unsure, pick this.
- HMAC-SHA1 — still cryptographically sound as an HMAC even though plain SHA-1 is broken for collisions, because HMAC's security doesn't rely on collision resistance. You'll meet it in older systems (GitHub still sends an
X-Hub-SignatureSHA-1 header for compatibility). Don't choose it for new designs. - HMAC-SHA512 — useful when you want extra headroom or you're on 64-bit hardware where SHA-512 can actually outperform SHA-256. Larger tags cost more bytes to transmit and store.
Is HMAC-SHA256 the same as SHA-256?
No—and this is a common mix-up behind searches like "hmac vs sha256." SHA-256 is a plain hash: one input, no key, and anyone can reproduce it. HMAC-SHA256 uses SHA-256 internally but mixes in a secret key, so only key-holders can produce or check the tag. Use plain SHA-256 for checksums and content fingerprints; use HMAC-SHA256 when authenticity matters.
Where HMAC Is Used
HMAC shows up anywhere a system needs to prove a message is both intact and from a known sender:
- API request signing — clients sign each request with a shared secret
- Webhooks — providers like GitHub and Stripe sign event payloads so you can trust them
- JWT signatures — the
HS256algorithm is literally HMAC-SHA256 - Session and cookie integrity — servers HMAC cookie data so clients can't tamper with it
API Request Signing
Many APIs (AWS, Stripe, GitHub) sign requests with HMAC:
1. Client prepares request
POST /api/transfer
Body: {"amount": 100, "to": "Alice"}
Timestamp: 1705312800
2. Client creates signature
const message = timestamp + method + path + body;
const signature = hmacSha256(secretKey, message);
3. Client sends with headers
POST /api/transfer HTTP/1.1
X-Timestamp: 1705312800
X-Signature: a1b2c3d4e5f6...
Content-Type: application/json
{"amount": 100, "to": "Alice"}
4. Server verifies
const expectedSig = hmacSha256(secretKey, message);
if (signature !== expectedSig) {
return 401 Unauthorized;
}
Webhooks
When a provider calls your endpoint, HMAC lets you confirm the payload really came from them and wasn't replayed or forged.
GitHub Webhooks
X-Hub-Signature-256: sha256=a1b2c3...
Verify:
expected = 'sha256=' + hmac_sha256(secret, body)
Stripe Webhooks
Stripe-Signature: t=1705312800,v1=a1b2c3...
Verify:
payload = timestamp + '.' + body
expected = hmac_sha256(secret, payload)
AWS Signature V4 uses HMAC-SHA256 in a multi-step process: create a canonical request, build a string to sign, derive a signing key through a chain of HMACs, then compute the final signature.
JWT Signatures (HS256)
A signed JWT using the HS256 algorithm is just HMAC-SHA256 over the token's header and payload. The three Base64url parts are joined, HMAC'd with your secret, and the tag becomes the signature. That's how the server later trusts a token it issued. For the full breakdown, see our guide on JWT tokens explained.
Session and Cookie Integrity
Frameworks that store data in cookies often append an HMAC tag so a user can't edit role=user into role=admin. On each request the server recomputes the tag over the cookie contents and rejects any mismatch.
Implementing HMAC Signatures
JavaScript (Node.js)
const crypto = require('crypto');
function createHmacSignature(secret, message) {
return crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
}
function verifyHmacSignature(secret, message, signature) {
const expected = createHmacSignature(secret, message);
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Python
import hmac
import hashlib
def create_hmac_signature(secret, message):
return hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
def verify_hmac_signature(secret, message, signature):
expected = create_hmac_signature(secret, message)
return hmac.compare_digest(signature, expected)
C# / .NET
using System.Security.Cryptography;
using System.Text;
string CreateHmacSignature(string secret, string message)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToHexString(hash).ToLower();
}
How to Verify an HMAC
Verification is not decryption—you never "reverse" an HMAC. Instead you recompute it and compare. The steps are always the same:
- Take the received message (and any signed metadata like the timestamp).
- Recompute the HMAC using your copy of the secret key.
- Compare your result to the received tag using a constant-time comparison.
- Accept only on an exact match; reject otherwise.
function verify(secret, message, receivedTag) {
const expected = crypto
.createHmac('sha256', secret)
.update(message)
.digest();
const received = Buffer.from(receivedTag, 'hex');
// Lengths must match before timingSafeEqual, or it throws
if (received.length !== expected.length) return false;
return crypto.timingSafeEqual(received, expected);
}
Because both sides hold the same secret, the recomputed tag will match only when the message is unchanged and the sender knew the key. Any single flipped bit in the message produces a completely different tag.
Timing Attacks and Constant-Time Comparison
Never use regular string comparison for signatures!
// VULNERABLE - timing attack possible
if (signature === expectedSignature) { ... }
// SAFE - constant time comparison
if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { ... }
Why? Regular comparison returns early when it finds a mismatch. An attacker can measure response times to gradually discover the correct signature, byte by byte.
Constant-time comparison always takes the same time regardless of where differences occur.
HMAC Best Practices
1. Use strong, random keys
// Generate a secure key
const key = crypto.randomBytes(32).toString('hex');
At minimum, use 256 bits (32 bytes) for HMAC-SHA256.
2. Include timestamp in signed message
Prevents replay attacks:
const message = timestamp + request;
// Reject if timestamp is too old
if (Date.now() - timestamp > 300000) reject();
3. Sign all relevant request parts
Include method, path, headers, and body in the signature:
const message = `${method}:${path}:${headers}:${body}`;
4. Use constant-time comparison
Always use timingSafeEqual() or hmac.compare_digest().
5. Keep keys secret
- Store securely (environment variables, secret managers)
- Rotate periodically
- Use different keys for different purposes
6. Include version in signature
Allows algorithm upgrades:
X-Signature-Version: hmac-sha256-v1
Common HMAC Mistakes
1. Using plain hash instead of HMAC
// WRONG - no authentication
const hash = sha256(message);
// RIGHT
const hmac = hmacSha256(secret, message);
2. Weak or hardcoded keys
// WRONG
const key = "password123";
// RIGHT
const key = process.env.HMAC_SECRET; // Random 256-bit key
3. Not including all data
// WRONG - body can be modified
const sig = hmac(key, timestamp);
// RIGHT
const sig = hmac(key, timestamp + method + path + body);
4. String comparison for verification
# WRONG - timing attack
if signature == expected: ...
# RIGHT
if hmac.compare_digest(signature, expected): ...
Frequently Asked Questions
What is HMAC?
HMAC is a way to prove that a message is authentic and unmodified. It combines a cryptographic hash function with a secret key so that only parties holding the key can create or verify the resulting tag. It's widely used for API request signing, webhooks, and token signatures.
How does HMAC work?
HMAC runs the hash function twice. First it hashes the message together with a key that has been XOR-ed with an inner pad (ipad, 0x36). Then it hashes that intermediate result together with the key XOR-ed with an outer pad (opad, 0x5c). This two-pass design protects against length-extension attacks and is provably secure when the underlying hash is sound.
What does HMAC stand for?
HMAC stands for keyed Hash-based Message Authentication Code. The "hash-based" part means it's built on a hash function like SHA-256, and the "keyed" part means a secret key is mixed in so the code can't be forged.
What is the difference between MAC and HMAC?
A MAC (Message Authentication Code) is any keyed algorithm that produces an authentication tag. HMAC is one specific type of MAC that is built from a hash function. Every HMAC is a MAC, but other MACs exist too, such as CMAC (block-cipher based) and Poly1305.
Is HMAC encryption?
No. HMAC is authentication, not encryption. It's a one-way function that produces a tag proving integrity and authenticity—it does not hide or scramble the message, and there is no way to "decrypt" an HMAC. If you need confidentiality, encrypt the data separately (or use authenticated encryption like AES-GCM). For related one-way techniques, see password hashing and salting.
What is HMAC-SHA256?
HMAC-SHA256 is HMAC using SHA-256 as its underlying hash function. It produces a 256-bit (32-byte) tag and is the recommended default for signing API requests, webhooks, and JWTs (where it's called HS256). You can compute one on our dedicated HMAC-SHA256 tool.
What is HMAC used for in network security?
In network security, HMAC authenticates messages traveling between systems so a receiver can trust both their integrity and their origin. It underpins protocols and features such as TLS record authentication, IPsec, API request signing, and signed webhooks. Because it's fast and hardware-friendly, HMAC is the standard choice for verifying that data on the wire wasn't altered or spoofed.
Summary
HMAC provides authentication that plain hashes can't:
- Hash: Verifies integrity (message wasn't corrupted)
- HMAC: Verifies integrity AND authenticity (message came from trusted source)
Key points:
- HMAC is a keyed MAC built from a hash—every HMAC is a MAC, but not vice versa
- Use HMAC-SHA256 for new projects; SHA-1 only for legacy compatibility
- HMAC is authentication, not encryption—you verify by recomputing, never decrypting
- Keep keys secret and strong (256+ bits)
- Always use constant-time comparison
- Include a timestamp to prevent replay attacks and sign all relevant request data
Need to generate HMAC signatures? Try our HMAC Generator, or use the focused HMAC-SHA256 generator for the SHA-256 variant.