HMAC vs Raw Hashing: How to Secure Webhook Payloads (Stripe & GitHub Style)
Learn why raw hashing secret + payload is vulnerable to length extension attacks. Understand how HMAC-SHA256 secures API webhooks.
Raw hashing secret + payload (e.g., sha256(secret + body)) is vulnerable to Length Extension Attacks in Merkle-Damgård hashes. HMAC (Hash-based Message Authentication Code, RFC 2104) uses a nested two-pass hashing structure H((K ^ opad) || H((K ^ ipad) || m)) that prevents message forgery, serving as the standard for Stripe, GitHub, and Shopify webhooks.
Solve this problem now with our free Hash & Checksum Generator
Try it instantly in your browser — zero tracking, 100% client-side privacy.
Specification & Architecture Reference
| Security Property | Raw Hash Concatenation (sha256(secret + m)) | HMAC-SHA256 (RFC 2104) |
|---|---|---|
| Length Extension Vulnerability | ❌ High Risk (Attacker can append data without knowing secret) | ✅ Immune (Double-pass hashing isolates state) |
| Timing Attack Protection | Requires manual constant-time comparison | Requires constant-time comparison (crypto.timingSafeEqual) |
| Standard API Adoption | Deprecated across all modern APIs | Standard for Stripe, GitHub, AWS SigV4, Shopify webhooks |
The Naive Flaw of Simple Concatenation
When securing API webhooks, developers sometimes try to sign payloads by concatenating a shared secret with the message body: ```text signature = sha256(SECRET_KEY + payload) ``` While this looks reasonable at first glance, it is vulnerable to a classic cryptographic exploit: the **Length Extension Attack**.
How Length Extension Attacks Work Algorithms like MD5, SHA-1, and SHA-256 process input messages through the **Merkle-Damgård construction**. The output hash is the internal register state after consuming the final block.
An attacker who intercepts `payload` and the resulting `signature` can: 1. Reconstruct the hash engine's internal state directly from the signature without knowing `SECRET_KEY`. 2. Append arbitrary extra data (`&role=admin`) along with standard padding bytes. 3. Compute a valid signature for the extended message that your server will accept as authentic.
The HMAC Solution (RFC 2104) HMAC addresses this by employing a nested, two-pass hashing construction with inner and outer padding: $$\text{HMAC}(K, m) = H\big((K' \oplus opad) \;\Vert\; H((K' \oplus ipad) \;\Vert\; m)\big)$$ Because the outer hash consumes the result of the inner hash, intermediate internal states are isolated, preventing length extension attacks.
Verifying Webhooks Safely in Node.js ```javascript const crypto = require("crypto");
function verifyWebhook(payload, signatureHeader, secret) { const hmac = crypto.createHmac("sha256", secret); hmac.update(payload, "utf8"); const computedSignature = hmac.digest("hex");
// Prevent timing attacks using constant-time comparison return crypto.timingSafeEqual( Buffer.from(computedSignature, "hex"), Buffer.from(signatureHeader, "hex") ); } ```
Test and debug your hash computations safely in your browser using Mickytool's **Hash & Checksum Generator**.
Solve this problem now with our free Hash & Checksum Generator
Execute this workflow immediately without saving data on remote servers.
Frequently Asked Questions
Why is crypto.timingSafeEqual mandatory when comparing signatures?
Standard string equality (===) returns false on the first mismatched character. Attackers can measure response time variations (timing attacks) to deduce the signature byte-by-byte.
Is HMAC required when using SHA-3 or BLAKE2?
BLAKE2 and SHA-3 (Keccak) are not Merkle-Damgård constructions and are naturally immune to length extension, though keyed BLAKE2 and HMAC-SHA-3 remain recommended practice.