HMAC stands for Hash-based Message Authentication Code. It is a specific type of message authentication code (MAC) that uses a cryptographic hash function (such as SHA-256 or SHA-512) combined with a secret key. Defined in RFC 2104 (published in 1997) and further standardized in FIPS PUB 198-1, HMAC has become one of the most widely used authentication mechanisms in modern computing.
The fundamental purpose of HMAC is to provide two guarantees about a message: data integrity (the message has not been altered since the HMAC was computed) and data authentication (the HMAC was generated by someone who possesses the secret key). These two properties together make HMAC essential for securing communication channels where both parties share a secret key.
You encounter HMAC-based authentication every day, even if you do not realize it. When your application receives a webhook from GitHub, Stripe, or Slack, the service signs the payload with HMAC-SHA256 using a shared secret. When you make an API call to AWS, the request is signed with HMAC-SHA256 as part of Signature Version 4. When your browser sends a cookie, the server may use HMAC to verify that the cookie value has not been tampered with. HMAC is everywhere in web infrastructure.
Think of HMAC as a tamper-evident seal on a package, combined with a shared secret handshake. Anyone can see the package (the message), but only someone who knows the secret can create a valid seal. When the recipient receives the package, they use the same secret to create their own seal. If the seals match, the package is authentic and unmodified. If they differ, either the package was tampered with or the sender did not know the secret.
Understanding the internal construction of HMAC helps explain why it is more secure than simply concatenating a key with a message and hashing the result. The HMAC algorithm, as defined in RFC 2104, uses two passes of hashing with specially constructed inner and outer keys.
The mathematical definition of HMAC is:
HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m))
Where:
The HMAC computation proceeds in several steps:
The two-pass construction is what makes HMAC resistant to length extension attacks. Even if an attacker knows the hash of a message, they cannot extend it with additional data and compute a valid HMAC without knowing the secret key.
A common question is why HMAC uses this complex two-pass construction instead of simply computing SHA256(key + message). The answer is that hash functions based on the Merkle-Damgard construction (which includes SHA-256 and SHA-512) are vulnerable to length extension attacks. An attacker who knows SHA256(key + message) can compute SHA256(key + message + padding + extension) without knowing the key. The HMAC construction prevents this attack entirely.
Understanding the distinction between HMAC and plain hashing is critical for choosing the right tool in security applications. While both produce fixed-size outputs from variable-size inputs, they serve fundamentally different purposes.
Use a plain hash when you need to verify data integrity without authentication: file checksum verification, content-addressable storage, deduplication, and cache key generation. Use HMAC when you need to verify both integrity and authenticity: API request signing, webhook verification, session token generation, and any scenario where you need to prove that a message was created by a trusted party.
HMAC can be used with any cryptographic hash function, but the three most common choices are SHA-256, SHA-384, and SHA-512. The algorithm names are typically written as HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512.
HMAC-SHA256 is the most widely used and recommended algorithm. It produces a 256-bit (32-byte) signature, typically represented as a 64-character hex string or a 44-character base64 string. It is the standard for AWS Signature V4, GitHub webhook signatures, Stripe webhook signatures, Slack request verification, and most modern API authentication schemes. Unless you have a specific reason to use a different algorithm, HMAC-SHA256 is the right default choice.
HMAC-SHA384 produces a 384-bit (48-byte) signature. It is a truncated version of SHA-512 internally and is sometimes used in TLS cipher suites (e.g., TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). It is less common in API authentication but provides a middle ground between SHA-256 and SHA-512.
HMAC-SHA512 produces a 512-bit (64-byte) signature. It uses 64-bit arithmetic internally, which makes it faster than SHA-256 on 64-bit processors. It is a good choice when you need a longer signature, are working in environments where 64-bit performance matters, or when the specification you are implementing requires SHA-512 (some government and financial protocols do).
HMAC-SHA1 and HMAC-MD5 are still technically secure as HMAC constructions (the HMAC construction compensates for the weaknesses in SHA-1 and MD5), but they are deprecated by modern standards. New implementations should use HMAC-SHA256 or stronger. Some legacy systems still use HMAC-SHA1 (e.g., older OAuth implementations), but migration to SHA-256 is strongly recommended.
One of the most common real-world applications of HMAC is webhook signature verification. When a service like GitHub, Stripe, or Slack sends a webhook to your server, it signs the request body with a shared secret using HMAC. Your server must verify this signature to ensure the webhook is authentic and has not been tampered with.
HMAC-SHA256(secret, request_body).X-Hub-Signature-256).
GitHub sends webhook signatures in the X-Hub-Signature-256 header with the format sha256=<hex-hmac>. Here is how to verify it in Node.js:
const crypto = require('crypto');
function verifyGitHubWebhook(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload, 'utf-8');
const computed = 'sha256=' + hmac.digest('hex');
// Use constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(signature)
);
}
// In your webhook handler:
app.post('/webhook', (req, res) => {
const signature = req.headers['x-hub-signature-256'];
const payload = req.body; // raw body string
if (!verifyGitHubWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook...
res.status(200).send('OK');
});
Stripe uses a more complex signing scheme with timestamps to prevent replay attacks. The signature header Stripe-Signature contains both a timestamp and the HMAC value.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/stripe-webhook', (req, res) => {
const sig = req.headers['stripe-signature'];
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
try {
const event = stripe.webhooks.constructEvent(
req.body, sig, endpointSecret
);
// Process event...
res.json({ received: true });
} catch (err) {
res.status(400).send(`Webhook Error: ${err.message}`);
}
});
Beyond webhooks, HMAC is widely used for signing outbound API requests. The most prominent example is AWS Signature Version 4, which uses HMAC-SHA256 to authenticate every API request to AWS services.
AWS Sig V4 is a multi-step HMAC-based signing process. At its core, it computes a chain of HMAC values that incorporate the date, region, service, and request details.
// Simplified AWS Sig V4 signing key derivation
function getSignatureKey(key, dateStamp, region, service) {
const kDate = hmacSHA256('AWS4' + key, dateStamp);
const kRegion = hmacSHA256(kDate, region);
const kService = hmacSHA256(kRegion, service);
const kSigning = hmacSHA256(kService, 'aws4_request');
return kSigning;
}
// The final signature
const signingKey = getSignatureKey(secretKey, '20260327', 'us-east-1', 's3');
const signature = hmacSHA256(signingKey, stringToSign).toString('hex');
This chained HMAC approach means that even if one signing key is compromised, it is only valid for a specific date, region, and service combination. The design limits the blast radius of a key compromise.
If you are building your own API, you can implement a simple HMAC-based authentication scheme. The client includes a signature computed over the request method, path, timestamp, and body. The server verifies this signature using the shared secret.
// Client-side: Sign the request
function signRequest(method, path, body, secret) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = [method, path, timestamp, body].join('\n');
const signature = hmacSHA256(secret, payload);
return {
'X-Timestamp': timestamp,
'X-Signature': signature
};
}
// Server-side: Verify the request
function verifyRequest(req, secret) {
const timestamp = req.headers['x-timestamp'];
const signature = req.headers['x-signature'];
// Check timestamp is within 5 minutes to prevent replay attacks
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false;
}
const payload = [req.method, req.path, timestamp, req.body].join('\n');
const expected = hmacSHA256(secret, payload);
return timingSafeEqual(signature, expected);
}
Every major programming language provides built-in or standard library support for HMAC. Here are examples in the most common languages used in web development.
Modern browsers provide the Web Crypto API, which includes native HMAC support through crypto.subtle. This is what our HMAC Generator tool uses.
async function generateHMAC(message, secret) {
const encoder = new TextEncoder();
const keyData = encoder.encode(secret);
const messageData = encoder.encode(message);
const key = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', key, messageData);
const hashArray = Array.from(new Uint8Array(signature));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// Usage:
const hmac = await generateHMAC('Hello, World!', 'my-secret-key');
console.log(hmac);
const crypto = require('crypto');
function generateHMAC(message, secret) {
return crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
}
const hmac = generateHMAC('Hello, World!', 'my-secret-key');
console.log(hmac);
import hmac
import hashlib
def generate_hmac(message, secret):
return hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
result = generate_hmac('Hello, World!', 'my-secret-key')
print(result)
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func generateHMAC(message, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(message))
return hex.EncodeToString(mac.Sum(nil))
}
func main() {
result := generateHMAC("Hello, World!", "my-secret-key")
fmt.Println(result)
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class HMACExample {
public static String generateHMAC(String message, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
mac.init(keySpec);
byte[] hmacBytes = mac.doFinal(message.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : hmacBytes) {
hex.append(String.format("%02x", b));
}
return hex.toString();
}
}
While HMAC is a well-designed and proven construction, improper implementation can introduce vulnerabilities. Here are the most important security considerations when working with HMAC.
When verifying an HMAC signature, you must use a constant-time comparison function rather than a simple string equality check (===). A regular comparison returns early on the first mismatched character, which creates a timing side-channel. An attacker can measure response times to determine how many characters of the HMAC are correct, effectively brute-forcing the signature one byte at a time.
// WRONG: Vulnerable to timing attacks
if (computedHMAC === expectedHMAC) { /* OK */ }
// CORRECT: Constant-time comparison
const crypto = require('crypto');
if (crypto.timingSafeEqual(
Buffer.from(computedHMAC),
Buffer.from(expectedHMAC)
)) { /* OK */ }
The security of HMAC depends entirely on the secrecy of the key. Never hardcode keys in source code, commit them to version control, or expose them in client-side JavaScript. Store keys in environment variables, secret management services (AWS Secrets Manager, HashiCorp Vault), or encrypted configuration files.
The HMAC specification recommends that keys be at least as long as the hash output (32 bytes for SHA-256, 64 bytes for SHA-512). Shorter keys are padded internally, but longer keys provide better security. Generate keys using a cryptographically secure random number generator -- never use passwords or predictable values as HMAC keys.
HMAC alone does not prevent replay attacks. An attacker who captures a valid request with its HMAC signature can resend it later. To prevent this, include a timestamp or nonce in the signed data and reject requests with timestamps outside an acceptable window (typically 5 minutes). Stripe's webhook signing includes timestamps for exactly this reason.
Sign everything that matters. If you only sign the request body but not the URL or method, an attacker could redirect a POST body to a different endpoint. Sign the HTTP method, URL path, query parameters, content type, timestamp, and body for comprehensive protection.
Unless you have a specific requirement for a different algorithm, use HMAC-SHA256. It is the industry standard, widely supported, and provides strong security. Do not use HMAC-MD5 or HMAC-SHA1 in new implementations.
Use a cryptographically secure random number generator to create HMAC keys. In Node.js: crypto.randomBytes(32).toString('hex'). In Python: secrets.token_hex(32). Never derive keys from passwords or use predictable values.
This cannot be emphasized enough. Every HMAC verification must use constant-time comparison. The performance difference is negligible, but the security difference is enormous. Use crypto.timingSafeEqual() in Node.js, hmac.compare_digest() in Python, or hmac.Equal() in Go.
Always include a timestamp in the signed data and validate that the timestamp is within an acceptable window (typically 5 minutes). This prevents replay attacks where an attacker resends a captured valid request.
Log failed signature verifications (without logging the actual secret key). Monitoring for spikes in failed verifications can alert you to potential attacks. But be careful not to log the raw signature values -- an attacker with access to logs could use them for offline analysis.
Implement a key rotation strategy. Support multiple active keys during the rotation period so that requests signed with the old key are still accepted while the new key is being deployed. Most webhook providers support configuring new secrets while keeping old ones active temporarily.
Our free HMAC Generator tool makes it easy to compute HMAC signatures for testing and debugging. It uses the Web Crypto API (crypto.subtle) built into your browser for secure, native HMAC computation.
The tool supports SHA-256, SHA-384, and SHA-512 algorithms with output in both hexadecimal and base64 encoding. You can enter your secret key as plain text (UTF-8) or as a hex-encoded string. The HMAC is computed in real time as you type, so you get instant feedback.
The Verify Mode feature is particularly useful for debugging webhook signatures. Paste the expected HMAC from a webhook header and the tool will instantly tell you whether it matches the computed value. This saves time when troubleshooting API integrations.
All computation happens entirely in your browser. Your secret key and message data are never sent to any server. The tool works offline once the page has loaded. You can verify this by checking the Network tab in your browser's developer tools.
Generate and verify HMAC signatures instantly. Free, no sign-up required.
Open HMAC Generator →