Python HMAC Authentication — Core Concepts

What HMAC Solves

When you receive data over a network, you face two questions: has this been modified? And did the expected sender create it? A plain hash (SHA-256 of the data) answers neither — anyone can compute it. HMAC answers both by incorporating a shared secret key into the hashing process.

HMAC is defined in RFC 2104. The construction is: HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m)), where H is a hash function, K is the key, and opad/ipad are fixed padding constants. This double-hash structure provides security even if the underlying hash has certain weaknesses.

Python’s hmac Module

The API mirrors hashlib but adds a key parameter:

Creating an HMAC involves three arguments: the secret key (as bytes), the message (as bytes), and the hash function to use. The result is a digest that depends on all three — changing any one produces a completely different output.

The module provides two critical methods:

  • hmac.new(key, msg, digestmod) — creates an HMAC object that can be updated incrementally
  • hmac.digest(key, msg, digest) — one-shot computation, faster for single messages (Python 3.7+)
  • hmac.compare_digest(a, b) — constant-time comparison that prevents timing attacks

Webhook Verification Pattern

Most webhook providers (GitHub, Stripe, Twilio, Slack) sign payloads with HMAC-SHA256. The pattern is consistent:

  1. The provider sends the raw request body plus a signature header
  2. Your server computes HMAC(secret, body) using the shared webhook secret
  3. You compare your computed signature with the header value using constant-time comparison
  4. If they match, the request is authentic

The key detail: you must hash the raw request body, not a parsed/re-serialized version. JSON serialization is not canonical — key ordering, whitespace, and encoding can differ, producing a different hash.

Why Constant-Time Comparison Matters

Regular string comparison (==) stops at the first mismatched character. An attacker can measure response times to determine how many characters of their forged signature matched. Over thousands of requests, they reconstruct the correct signature byte by byte.

hmac.compare_digest() always examines every byte, taking the same time whether zero or all characters match. This eliminates the timing side channel entirely.

HMAC vs Plain Hashing

PropertySHA-256(msg)HMAC-SHA256(key, msg)
Anyone can computeYesNo — requires the key
Proves integrityYesYes
Proves authenticityNoYes
Vulnerable to length extensionYesNo
Suitable for API signingNoYes

The length extension immunity is particularly important. With plain SHA-256, an attacker who knows SHA256(secret + message) can compute SHA256(secret + message + padding + extra) without knowing the secret. HMAC’s double-hash construction makes this impossible.

Common Misconception

“I can just concatenate the key and message and hash them.” This is the most dangerous shortcut in message authentication. SHA256(key + message) is vulnerable to length extension attacks. SHA256(message + key) has different theoretical weaknesses. Only the HMAC construction, with its inner and outer hashing passes, is proven secure under standard cryptographic assumptions. Use hmac.new() — never roll your own concatenation scheme.

Practical Considerations

Key management — HMAC keys should be at least 32 bytes of cryptographically random data (use secrets.token_bytes(32)). Store them in environment variables or a secrets manager, never in source code.

Algorithm choice — HMAC-SHA256 is the standard. HMAC-SHA512 offers a wider security margin at minimal performance cost. Avoid HMAC-MD5 and HMAC-SHA1 for new systems, though HMAC’s construction means these are still more secure than raw MD5/SHA-1.

Encoding consistency — Both sides must agree on the exact bytes being signed. Specify encoding (UTF-8), canonicalization (no pretty-printing JSON), and whether headers or metadata are included in the signed payload.

Key rotation — When rotating webhook secrets, accept signatures computed with either the old or new key during a transition window. This prevents dropping legitimate webhooks during the rotation.

The one thing to remember: HMAC turns a hash function into an authentication tool — it’s the standard way to prove that data is genuine and untampered, and Python makes it available in three lines of code.

pythonsecuritycryptography

See Also