HMAC Generator
Generate HMAC (Hash-based Message Authentication Code) signatures for API authentication and message integrity verification. HMAC combines a cryptographic hash with a secret key to create unforgeable signatures.
Understanding HMAC
| Property | Description |
|---|---|
| Purpose | Message authentication |
| Components | Message + Secret key + Hash |
| Output | Fixed-size signature |
| Security | Proves message origin & integrity |
HMAC vs Plain Hashing
| Feature | Plain Hash | HMAC |
|---|---|---|
| Uses secret key | No | Yes |
| Verifies sender | No | Yes |
| Prevents tampering | Detects | Detects + authenticates |
| API security | Insufficient | Recommended |
HMAC Implementation
``javascript
// Node.js
const crypto = require('crypto');
function generateHmac(message, secret, algorithm = 'sha256') {
return crypto
.createHmac(algorithm, secret)
.update(message)
.digest('hex');
}
// Example
const signature = generateHmac('Hello World', 'my-secret-key');
// "a1b2c3d4e5f6..."
// Verify HMAC
function verifyHmac(message, secret, signature) {
const expected = generateHmac(message, secret);
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
`
Web Crypto API (Browser)
`javascript
async function generateHmacBrowser(message, secret) {
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign(
'HMAC',
key,
encoder.encode(message)
);
return Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
`
API Request Signing
`javascript
// Sign an API request
function signRequest(method, path, body, timestamp, secret) {
const message = ${method}\n${path}\n${timestamp}\n${body};
return generateHmac(message, secret);
}
// Example
const signature = signRequest(
'POST',
'/api/orders',
JSON.stringify({ item: 'book', qty: 1 }),
Date.now().toString(),
'api-secret-key'
);
`
Common HMAC Algorithms
| Algorithm | Security | Use Case |
|---|---|---|
| HMAC-MD5 | Weak | Legacy only |
| HMAC-SHA1 | Acceptable | OAuth 1.0 |
| HMAC-SHA256 | Strong | AWS, Stripe, most APIs |
| HMAC-SHA512 | Very strong | High-security needs |
The Avalanche Effect
A one-character change produces a completely different digest — not a similar one. That
property is what makes a hash useful as a fingerprint:
| Input | MD5 | CRC32 |
|---|---|---|
hello | 5d41402abc4b2a76b9719d911017c592 | 3610a686 |
hello. | d94c10e437d18531e122ed0b45badd2a | 0a39d4f1 |
Hello | 8b1a9953c4611296a827abf8c47804d7 | f7d18982 |
hello and Hello differ by one bit of one byte, and share no part of their output.
RIPEMD-160 of hello is 108f07b8382412612c048d07d13f814118445acd, and of Hello is
d44426aca8ae0a69cdbc4021c64fa5ad68ca32fe` — same story.Digest Length and Collision Resistance
| Algorithm | Output | Birthday bound | Status |
|---|---|---|---|
| CRC32 | 32 bits | ~77,000 values | Checksum only |
| MD5 | 128 bits | 2⁶⁴ in theory | Broken — collisions in seconds |
| SHA-1 | 160 bits | 2⁸⁰ in theory | Broken — SHAttered, 2017 |
| RIPEMD-160 | 160 bits | 2⁸⁰ | No practical attack |
| SHA-256 | 256 bits | 2¹²⁸ | Current standard |
| SHA-512 | 512 bits | 2²⁵⁶ | Standard, faster on 64-bit |
Never Hash a Password With These
A general-purpose hash is designed to be fast, which is exactly wrong for passwords: speed helps the attacker. Use a deliberately slow KDF — bcrypt, scrypt or Argon2id — with a per-password salt. A GPU tries billions of SHA-256 guesses a second and a few thousand bcrypt guesses a second, and that gap is the entire defence.