← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

HMAC (Hash-based Message Authentication Code)

security OWASP A2:2021 PHP 5.1+ Intermediate
debt(d5/e3/b3/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm, both specialist/SAST tools. The code_pattern shows they can catch the naive hash($secret.$message) pattern, but the timing-oracle mistake (using === instead of hash_equals()) also requires a specialist SAST rule rather than a default linter, so d5 is the right anchor.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix is a direct function swap: replace hash('sha256', ...) with hash_hmac('sha256', $message, $secret) and replace == with hash_equals(). Each fix is a small, localised change — typically one or two call sites per file — but may need to be applied in several places across the codebase, placing it slightly above a pure one-liner (e1) but not requiring significant refactor.

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

Closest to 'localised tax' (b3). HMAC usage is typically scoped to specific signing/verification points (webhook handlers, API token validation, etc.). While it applies across web, cli, and queue-worker contexts, the actual code surface is limited to dedicated auth/integrity functions rather than permeating every layer of the codebase.

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

Closest to 'serious trap' (t7). The misconception field calls out that == comparison appears safe to a competent developer — equality comparison is the standard idiom for string checks in PHP — but it actually introduces a timing oracle. This directly contradicts normal string-comparison intuition and is a well-documented but easily missed gotcha. The naive hash concatenation mistake also contradicts how developers expect a keyed hash to work, reinforcing the t7 score.

About DEBT scoring →

Also Known As

Hash-based Message Authentication Code HMAC-SHA256 message authentication

TL;DR

A keyed hash that verifies both the integrity and authenticity of a message — only someone with the secret key can produce or verify it.

Explanation

HMAC (RFC 2104) combines a cryptographic hash function with a secret key: HMAC(key, message) = H((key XOR opad) || H((key XOR ipad) || message)). Unlike a plain hash, a valid HMAC cannot be forged without the key. In PHP, use hash_hmac('sha256', $message, $secretKey) and verify with hash_equals() to prevent timing attacks. Common uses include API request signing, webhook payload verification, signed cookies, and password-reset token authentication. Choose SHA-256 or SHA-512 — avoid MD5 or SHA-1.

Common Misconception

Comparing HMAC values with == is safe. String comparison short-circuits on the first differing byte, leaking timing information about how many bytes match. Always use hash_equals() for HMAC verification.

Why It Matters

A plain hash of a message can be recalculated by anyone — HMAC binds the hash to a secret key, so only parties with the key can verify or forge it.

Common Mistakes

  • Using hash('sha256', $secret . $data) instead of hash_hmac() — this is vulnerable to length-extension attacks.
  • Comparing HMACs with === instead of hash_equals(), introducing a timing oracle.
  • Using a weak or short key — HMAC security is bounded by the key entropy.
  • Reusing the same key for different purposes — a key used for HMAC should not also be used for encryption.

Code Examples

✗ Vulnerable
if (md5($payload) === $receivedHash) { /* forgeable — no key */ }
✓ Fixed
if (hash_equals(hash_hmac('sha256', $payload, $secret), $receivedHash)) { /* keyed, constant-time */ }

Added 15 Mar 2026
Edited 22 Mar 2026
Views 34
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
1 ping F 0 pings S 0 pings S 1 ping M 0 pings T 0 pings W 0 pings T 2 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 4 pings T 1 ping F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 3 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S
No pings yet today
Amazonbot 7 Perplexity 7 ChatGPT 4 Google 3 Unknown AI 2 Ahrefs 2 SEMrush 2 Majestic 1
crawler 26 crawler_json 2
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Low
⚡ Quick Fix
Use hash_hmac('sha256', $message, $secret) to create authenticated message signatures; always compare with hash_equals() not ==
📦 Applies To
PHP 5.1+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
md5($secret.$message) or sha1($secret.$message) as poor man's HMAC — length extension attack vulnerable
Auto-detectable: ✓ Yes semgrep psalm
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Low ✗ Manual fix Fix: Medium Context: Function Tests: Update
CWE-347 CWE-345

✓ schema.org compliant