Python’s hmac module computes and verifies HMAC (Hash-based Message Authentication Codes) — keyed hashes that authenticate both message integrity and sender identity. import hmac. Create: h = hmac.new(key, msg=None, digestmod="sha256") — key must be bytes; digestmod is a hash name string or hashlib constructor; key shorter than block size is padded, longer is hashed first. Single-call: hmac.digest(key, msg, digest) → raw bytes — faster than creating an object (uses OpenSSL directly when available). Update: h.update(data) — feed more bytes. Result: h.digest() → raw bytes; h.hexdigest() → hex string. Clone: h.copy() — fork state for multiple MAC computations from the same prefix. Attributes: h.digest_size → output length in bytes; h.block_size → underlying hash block size. Constant-time compare: hmac.compare_digest(a, b) → bool — CRITICAL: always use this instead of == for MAC comparison to prevent timing attacks; works with both bytes and str. Common digestmod values: "sha256", "sha512", "sha3_256", "blake2b". Claude Code generates request signing middlewares, webhook validators, API key verifiers, session cookie signers, and tamper-proof token systems.
CLAUDE.md for hmac
## hmac Stack
- Stdlib: import hmac, hashlib, secrets
- Single: mac = hmac.digest(key, msg, "sha256") # bytes, fast
- Object: h = hmac.new(key, digestmod="sha256")
- h.update(data); mac = h.digest()
- Hex: mac_hex = hmac.new(key, msg, "sha256").hexdigest()
- Verify: hmac.compare_digest(received, expected) # constant-time!
- Note: key must be bytes; never use == for MAC comparison
hmac Message Authentication Pipeline
# app/hmacutil.py — sign, verify, webhook, API keys, timed tokens
from __future__ import annotations
import base64
import hmac
import hashlib
import json
import secrets
import struct
import time
from dataclasses import dataclass
# ─────────────────────────────────────────────────────────────────────────────
# 1. Core sign/verify helpers
# ─────────────────────────────────────────────────────────────────────────────
def sign(key: bytes, message: bytes, digestmod: str = "sha256") -> bytes:
"""
Compute HMAC-{digestmod} of message under key.
Returns raw MAC bytes.
Example:
mac = sign(b"secret", b"hello world")
print(mac.hex())
"""
return hmac.digest(key, message, digestmod)
def sign_hex(key: bytes, message: bytes, digestmod: str = "sha256") -> str:
"""
Sign message and return hex-encoded MAC.
Example:
tag = sign_hex(b"secret", b"payload")
"""
return sign(key, message, digestmod).hex()
def verify(
key: bytes,
message: bytes,
mac: bytes,
digestmod: str = "sha256",
) -> bool:
"""
Verify that mac is the correct HMAC of message under key.
Uses constant-time comparison to prevent timing attacks.
Example:
ok = verify(b"secret", b"hello world", received_mac)
"""
expected = sign(key, message, digestmod)
return hmac.compare_digest(expected, mac)
def verify_hex(
key: bytes,
message: bytes,
mac_hex: str,
digestmod: str = "sha256",
) -> bool:
"""
Verify a hex-encoded HMAC.
Example:
ok = verify_hex(b"secret", b"payload", "a1b2c3...")
"""
try:
mac = bytes.fromhex(mac_hex)
except ValueError:
return False
return verify(key, message, mac, digestmod)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Webhook signature verification
# ─────────────────────────────────────────────────────────────────────────────
def sign_webhook_payload(
secret: str,
payload: bytes,
timestamp: int | None = None,
digestmod: str = "sha256",
) -> tuple[int, str]:
"""
Sign a webhook payload (Stripe/GitHub style).
Returns (timestamp, signature_hex).
Example:
ts, sig = sign_webhook_payload("wh_secret", request_body)
# headers: {"X-Timestamp": ts, "X-Signature": "sha256=" + sig}
"""
ts = timestamp if timestamp is not None else int(time.time())
signed_payload = f"{ts}.".encode() + payload
sig = sign_hex(secret.encode(), signed_payload, digestmod)
return ts, sig
def verify_webhook_signature(
secret: str,
payload: bytes,
timestamp: int,
signature: str,
tolerance_seconds: int = 300,
digestmod: str = "sha256",
) -> bool:
"""
Verify a webhook signature with timestamp replay protection.
Returns False if timestamp is too old or signature is invalid.
Example:
ts = int(request.headers["X-Timestamp"])
sig = request.headers["X-Signature"].removeprefix("sha256=")
ok = verify_webhook_signature("wh_secret", body, ts, sig)
"""
if tolerance_seconds > 0:
age = abs(time.time() - timestamp)
if age > tolerance_seconds:
return False
signed_payload = f"{timestamp}.".encode() + payload
expected = sign_hex(secret.encode(), signed_payload, digestmod)
return hmac.compare_digest(expected, signature)
# ─────────────────────────────────────────────────────────────────────────────
# 3. API request signer
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class SignedRequest:
method: str
path: str
timestamp: int
nonce: str
body_hash: str # hex SHA-256 of body
signature: str # hex HMAC-SHA256 of canonical string
def canonical_string(self) -> str:
return f"{self.method}\n{self.path}\n{self.timestamp}\n{self.nonce}\n{self.body_hash}"
def to_headers(self) -> dict[str, str]:
return {
"X-Timestamp": str(self.timestamp),
"X-Nonce": self.nonce,
"X-Signature": self.signature,
}
def sign_request(
key: bytes,
method: str,
path: str,
body: bytes = b"",
timestamp: int | None = None,
) -> SignedRequest:
"""
Sign an HTTP request using HMAC-SHA256 over a canonical string.
Returns a SignedRequest with headers to attach to the outgoing request.
Example:
sr = sign_request(api_key, "POST", "/api/orders", json.dumps(order).encode())
headers = sr.to_headers()
"""
ts = timestamp if timestamp is not None else int(time.time())
nonce = secrets.token_hex(8)
body_hash = hashlib.sha256(body).hexdigest()
canonical = f"{method.upper()}\n{path}\n{ts}\n{nonce}\n{body_hash}"
sig = sign_hex(key, canonical.encode())
return SignedRequest(
method=method.upper(),
path=path,
timestamp=ts,
nonce=nonce,
body_hash=body_hash,
signature=sig,
)
def verify_request(
key: bytes,
method: str,
path: str,
body: bytes,
timestamp: int,
nonce: str,
signature: str,
tolerance_seconds: int = 60,
) -> bool:
"""
Verify a signed HTTP request. Returns True if valid.
Example:
ok = verify_request(
api_key, "POST", "/api/orders", body,
int(headers["X-Timestamp"]), headers["X-Nonce"], headers["X-Signature"]
)
"""
age = abs(time.time() - timestamp)
if age > tolerance_seconds:
return False
body_hash = hashlib.sha256(body).hexdigest()
canonical = f"{method.upper()}\n{path}\n{timestamp}\n{nonce}\n{body_hash}"
expected = sign_hex(key, canonical.encode())
return hmac.compare_digest(expected, signature)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Timed token (TOTP-style HMAC-based)
# ─────────────────────────────────────────────────────────────────────────────
def hotp(key: bytes, counter: int, digits: int = 6) -> str:
"""
Compute an HMAC-SHA1 OTP (HOTP — RFC 4226) for a counter value.
Example:
code = hotp(seed_key, counter=42)
print(code) # 6-digit string like "123456"
"""
msg = struct.pack(">Q", counter)
mac = hmac.new(key, msg, digestmod="sha1").digest()
offset = mac[-1] & 0x0F
truncated = struct.unpack(">I", mac[offset:offset + 4])[0] & 0x7FFFFFFF
return str(truncated % (10 ** digits)).zfill(digits)
def totp(key: bytes, digits: int = 6, step: int = 30, time_: float | None = None) -> str:
"""
Compute a time-based OTP (TOTP — RFC 6238) for the current time step.
Example:
import base64
seed = base64.b32decode("JBSWY3DPEHPK3PXP")
code = totp(seed)
print(code) # 6-digit code valid for ~30 seconds
"""
t = time_ if time_ is not None else time.time()
counter = int(t // step)
return hotp(key, counter, digits)
def verify_totp(
key: bytes,
code: str,
digits: int = 6,
step: int = 30,
window: int = 1,
) -> bool:
"""
Verify a TOTP code within a tolerance window of ±window steps.
Example:
ok = verify_totp(seed, user_entered_code)
"""
t = time.time()
for offset in range(-window, window + 1):
expected = totp(key, digits=digits, step=step,
time_=t + offset * step)
if hmac.compare_digest(expected, code):
return True
return False
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import os
print("=== hmac demo ===")
key = secrets.token_bytes(32)
# ── sign / verify ─────────────────────────────────────────────────────────
print("\n--- sign / verify ---")
msg = b"Hello, HMAC world!"
tag = sign_hex(key, msg)
print(f" msg: {msg!r}")
print(f" tag: {tag}")
print(f" verify(correct): {verify_hex(key, msg, tag)}")
print(f" verify(tampered): {verify_hex(key, b'Tampered!', tag)}")
# ── timing-safe compare ───────────────────────────────────────────────────
print("\n--- hmac.compare_digest ---")
a = "abc123"
b = "abc123"
c = "abc124"
print(f" compare_digest({a!r}, {b!r}): {hmac.compare_digest(a, b)}")
print(f" compare_digest({a!r}, {c!r}): {hmac.compare_digest(a, c)}")
# ── webhook signing ───────────────────────────────────────────────────────
print("\n--- webhook sign/verify ---")
secret = "wh_test_secret_abc"
payload = json.dumps({"event": "order.created", "id": 42}).encode()
ts, sig = sign_webhook_payload(secret, payload)
print(f" timestamp={ts} sig={sig[:24]}...")
ok = verify_webhook_signature(secret, payload, ts, sig)
print(f" verify(correct): {ok}")
print(f" verify(old ts): {verify_webhook_signature(secret, payload, ts - 9999, sig)}")
# ── request signing ────────────────────────────────────────────────────────
print("\n--- sign_request / verify_request ---")
api_key = secrets.token_bytes(32)
body = json.dumps({"item": "widget", "qty": 10}).encode()
sr = sign_request(api_key, "POST", "/api/orders", body)
print(f" headers: {sr.to_headers()}")
ok = verify_request(
api_key, sr.method, sr.path, body,
sr.timestamp, sr.nonce, sr.signature,
)
print(f" verify: {ok}")
# ── HOTP / TOTP ───────────────────────────────────────────────────────────
print("\n--- TOTP ---")
import base64
seed = base64.b32decode("JBSWY3DPEHPK3PXP")
code = totp(seed)
print(f" current TOTP code: {code}")
print(f" verify_totp: {verify_totp(seed, code)}")
print(f" verify wrong: {verify_totp(seed, '000000')}")
print("\n=== done ===")
For the hashlib alternative — hashlib.sha256(data).hexdigest() computes a keyless cryptographic hash — use hashlib for content integrity (checksums, deduplication, fingerprinting) where no shared secret is needed; use hmac whenever you need to authenticate the sender of a message (webhooks, API signatures, session cookies) — a plain hash can be recomputed by anyone, while an HMAC requires knowledge of the secret key, making it unforgeable without the key. For the cryptography (PyPI) alternative — cryptography.hazmat.primitives.hmac.HMAC(key, hashes.SHA256()) with h.update(data) and h.verify(mac) provides the same HMAC functionality with additional algorithm agility and a strict hazmat security model — use cryptography when you need ECDSA, RSA signing, AES-GCM encryption, or other primitives beyond HMAC; use hmac from the stdlib for pure message authentication scenarios where the extra dependency is unnecessary overhead. The Claude Skills 360 bundle includes hmac skill sets covering sign()/sign_hex()/verify()/verify_hex() core helpers, sign_webhook_payload()/verify_webhook_signature() webhook authentication, SignedRequest with sign_request()/verify_request() API request signing, and hotp()/totp()/verify_totp() OTP code generators. Start with the free tier to try message authentication patterns and hmac pipeline code generation.