← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

HMAC Construction

debt(d6/e3/b3/t7)
d6 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches' (d5), +1 because semgrep patterns for hash(secret.message) exist but rolling-your-own MAC often escapes default rules and requires custom rules or code review to catch.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3), the quick_fix is swapping hash(...) for hash_hmac('sha256', $message, $key, true) plus hash_equals() for comparison — a small localized pattern replacement, slightly more than one line.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3), HMAC usage is typically confined to specific verification points (webhooks, tokens, cookies) rather than pervading the whole system.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7), the misconception that HMAC provides confidentiality plus the intuitive but broken hash(secret||message) construction contradict how developers naively reason about 'signing' — the obvious approach is dangerously wrong.

About DEBT scoring →

Also Known As

HMAC keyed hash hash-based MAC HMAC-SHA256

TL;DR

HMAC combines a secret key with a cryptographic hash via a nested keyed construction — providing message authentication that resists length-extension attacks.

Explanation

HMAC (Hash-based Message Authentication Code) is defined in RFC 2104 as HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m)), where K' is the key padded or hashed to the hash function's block size, ipad is 0x36 repeated, and opad is 0x5C repeated. This nested structure serves a specific purpose: even though Merkle-Damgard hashes like SHA-256 are vulnerable to length-extension attacks (where an attacker can append data to a hash without knowing the input), the outer hash of an HMAC binds the entire computation and blocks that attack. HMAC provides authenticity (the message came from someone with the key) and integrity (the message was not altered), but not confidentiality — the message itself is not encrypted. Security depends on: (1) using a strong underlying hash (SHA-256 or SHA-3, never MD5/SHA-1 for new systems), (2) keys of at least the hash's output length (32 bytes for SHA-256), (3) constant-time tag comparison to prevent timing attacks, and (4) never reusing an HMAC key for other purposes like encryption. In PHP, use hash_hmac('sha256', $message, $key, true) for the raw binary tag and hash_equals() for verification. HMAC is the building block for many protocols: TLS record MACs (in CBC modes), JWT HS256 signatures, AWS Signature V4, HKDF key derivation, and TOTP/HOTP one-time passwords. It is distinct from AEAD constructions like AES-GCM, which provide authentication as part of encryption — HMAC is used when you need authentication of plaintext or of already-encrypted data (encrypt-then-MAC).

Common Misconception

HMAC encrypts the message or provides confidentiality — it does neither; HMAC only authenticates a message, and the plaintext must still be sent alongside the tag or encrypted separately.

Why It Matters

Rolling your own MAC by prepending a secret to a message (secret || message hashed) is vulnerable to length-extension attacks on SHA-1/SHA-256 — HMAC's nested construction is what makes keyed authentication safe with those hashes.

Common Mistakes

  • Building a homemade MAC as hash(secret || message) — vulnerable to length-extension on SHA-2 hashes.
  • Comparing HMAC tags with === or strcmp — timing side channels leak tag bytes; use hash_equals().
  • Reusing the same key for HMAC and encryption — key separation is a core cryptographic hygiene rule.
  • Using MD5 or SHA-1 as the underlying hash for new systems — while HMAC-MD5 is not yet fully broken, it should not be used.
  • Truncating HMAC tags to too few bytes (e.g. 32 bits) — makes brute-force forgery feasible.

Avoid When

  • You need message confidentiality — HMAC does not encrypt; combine with AES-GCM or use an AEAD directly.
  • An AEAD cipher like AES-GCM or ChaCha20-Poly1305 already covers your needs — no separate MAC is required.
  • You need public verifiability — HMAC uses a shared secret; use digital signatures (RSA/ECDSA/Ed25519) instead.
  • You would derive multiple keys from one HMAC key — use HKDF, which is built on HMAC but designed for key derivation.

When To Use

  • Authenticating messages between services sharing a symmetric secret (e.g. webhook signatures).
  • Building JWT HS256 signatures or verifying signed URLs and session cookies.
  • Encrypt-then-MAC constructions when using a non-AEAD cipher mode like AES-CBC.
  • Verifying integrity and authenticity of password reset links or one-time tokens carrying user data.

Code Examples

✗ Vulnerable
// Homemade MAC — vulnerable to length extension:
function signMessage(string $message, string $secret): string {
    return hash('sha256', $secret . $message);
}

function verifyMessage(string $message, string $tag, string $secret): bool {
    $expected = hash('sha256', $secret . $message);
    return $expected === $tag; // Timing attack: leaks tag byte by byte
}

// Attacker who knows H(secret || message) can compute
// H(secret || message || padding || extension) without knowing the secret.
✓ Fixed
// Proper HMAC construction:
function signMessage(string $message, string $key): string {
    // Raw binary tag; use bin2hex() only if transport requires text.
    return hash_hmac('sha256', $message, $key, true);
}

function verifyMessage(string $message, string $tag, string $key): bool {
    $expected = hash_hmac('sha256', $message, $key, true);
    return hash_equals($expected, $tag); // Constant-time comparison
}

// Key generation: 32 bytes of CSPRNG output for HMAC-SHA256.
$key = random_bytes(32);

Added 21 Jul 2026
Views 37
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 2 pings T 2 pings W 5 pings T 2 pings F 2 pings S 2 pings S 0 pings M 0 pings T 0 pings W 2 pings T 0 pings F 1 ping S 2 pings S 0 pings M 1 ping T 0 pings W 2 pings T 0 pings F
No pings yet today
Meta AI 1 Bing 1
Bing 8 Google 6 Applebot 2 PetalBot 2 Meta AI 2 ChatGPT 1 SEMrush 1 Ahrefs 1
crawler 23
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Low
⚡ Quick Fix
Use hash_hmac('sha256', $message, $key, true) with a 32-byte random key and verify tags with hash_equals() — never build a keyed hash by hand.
📦 Applies To
PHP 5.1+ web cli queue-worker library
🔗 Prerequisites
🔍 Detection Hints
hash\(\s*['"]sha(1|256|512)['"]\s*,\s*\$[a-z_]+\s*\.\s*\$[a-z_]+
Auto-detectable: ✗ No semgrep psalm
⚠ Related Problems
🤖 AI Agent
Confidence: High False Positives: Low ✓ Auto-fixable Fix: Low Context: Function Tests: Update
CWE-353 CWE-327 CWE-208


✓ schema.org compliant