The hmac module creates keyed-hash message authentication codes. Use it to verify both data integrity and authenticity—proving a message hasn't been tampered with and comes from someone who knows the secret key.
Basic HMAC
import hmac
import hashlib
secret_key = b'my-secret-key'
message = b'Hello, World!'
# Create HMAC
signature = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
print(signature) # e.g., '7f83b162...'Verify HMAC
import hmac
import hashlib
def sign_message(key, message):
"""Create HMAC signature."""
return hmac.new(key, message, hashlib.sha256).hexdigest()
def verify_signature(key, message, signature):
"""Verify HMAC signature (constant time)."""
expected = sign_message(key, message)
return hmac.compare_digest(expected, signature)
key = b'secret'
msg = b'important data'
# Sign
sig = sign_message(key, msg)
# Verify
if verify_signature(key, msg, sig):
print("Valid signature")
else:
print("Invalid signature")Why compare_digest?
Never use == to compare HMACs—it's vulnerable to timing attacks:
import hmac
# BAD - timing attack vulnerable
if computed_hmac == provided_hmac:
pass
# GOOD - constant time comparison
if hmac.compare_digest(computed_hmac, provided_hmac):
passDifferent Hash Algorithms
import hmac
import hashlib
key = b'secret'
msg = b'message'
# SHA-256 (recommended)
h256 = hmac.new(key, msg, hashlib.sha256).hexdigest()
# SHA-512
h512 = hmac.new(key, msg, hashlib.sha512).hexdigest()
# SHA-1 (avoid for new code)
h1 = hmac.new(key, msg, hashlib.sha1).hexdigest()
# MD5 (avoid - cryptographically broken)
hmd5 = hmac.new(key, msg, hashlib.md5).hexdigest()Incremental Updates
import hmac
import hashlib
key = b'secret'
# Build HMAC incrementally
h = hmac.new(key, digestmod=hashlib.sha256)
h.update(b'first part ')
h.update(b'second part ')
h.update(b'third part')
signature = h.hexdigest()Sign Files
import hmac
import hashlib
def sign_file(key, filepath):
"""Create HMAC signature for a file."""
h = hmac.new(key, digestmod=hashlib.sha256)
with open(filepath, 'rb') as f:
while chunk := f.read(8192):
h.update(chunk)
return h.hexdigest()
def verify_file(key, filepath, signature):
"""Verify file signature."""
computed = sign_file(key, filepath)
return hmac.compare_digest(computed, signature)API Request Signing
import hmac
import hashlib
import time
import json
def sign_request(secret_key, method, path, body=None, timestamp=None):
"""Sign an API request."""
timestamp = timestamp or str(int(time.time()))
# Create canonical request
parts = [method.upper(), path, timestamp]
if body:
parts.append(json.dumps(body, sort_keys=True))
message = '\n'.join(parts).encode()
signature = hmac.new(
secret_key.encode(),
message,
hashlib.sha256
).hexdigest()
return {
'X-Timestamp': timestamp,
'X-Signature': signature
}
# Usage
headers = sign_request(
'my-api-secret',
'POST',
'/api/users',
{'name': 'Alice'}
)Webhook Verification
import hmac
import hashlib
def verify_webhook(secret, payload, signature):
"""Verify webhook signature (common pattern)."""
computed = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
# Handle 'sha256=' prefix some services use
if signature.startswith('sha256='):
signature = signature[7:]
return hmac.compare_digest(computed, signature)
# Verify incoming webhook
payload = b'{"event": "user.created"}'
sig = request.headers.get('X-Signature')
if verify_webhook('webhook-secret', payload, sig):
process_webhook(payload)Token Generation
import hmac
import hashlib
import time
import base64
def generate_token(secret, user_id, expires_in=3600):
"""Generate signed token."""
expires = int(time.time()) + expires_in
data = f"{user_id}:{expires}"
signature = hmac.new(
secret.encode(),
data.encode(),
hashlib.sha256
).digest()
token = base64.urlsafe_b64encode(
f"{data}:{signature.hex()}".encode()
).decode()
return token
def verify_token(secret, token):
"""Verify and decode token."""
try:
decoded = base64.urlsafe_b64decode(token.encode()).decode()
user_id, expires, signature = decoded.rsplit(':', 2)
data = f"{user_id}:{expires}"
expected = hmac.new(
secret.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
return None
if int(expires) < time.time():
return None
return user_id
except:
return NoneCookie Signing
import hmac
import hashlib
import base64
def sign_cookie(secret, name, value):
"""Sign a cookie value."""
signature = hmac.new(
secret.encode(),
f"{name}={value}".encode(),
hashlib.sha256
).hexdigest()[:16]
return f"{value}.{signature}"
def verify_cookie(secret, name, signed_value):
"""Verify signed cookie."""
try:
value, signature = signed_value.rsplit('.', 1)
expected = hmac.new(
secret.encode(),
f"{name}={value}".encode(),
hashlib.sha256
).hexdigest()[:16]
if hmac.compare_digest(expected, signature):
return value
except:
pass
return NoneDigest Sizes
import hmac
import hashlib
key = b'secret'
msg = b'message'
# Different output sizes
print(len(hmac.new(key, msg, hashlib.sha256).digest())) # 32 bytes
print(len(hmac.new(key, msg, hashlib.sha256).hexdigest())) # 64 chars
print(len(hmac.new(key, msg, hashlib.sha512).digest())) # 64 bytes
print(len(hmac.new(key, msg, hashlib.sha512).hexdigest())) # 128 charsCopy HMAC State
import hmac
import hashlib
key = b'secret'
h1 = hmac.new(key, b'common-prefix-', hashlib.sha256)
# Branch from same state
h2 = h1.copy()
h3 = h1.copy()
h2.update(b'suffix-a')
h3.update(b'suffix-b')
print(h2.hexdigest()) # Different
print(h3.hexdigest()) # DifferentWhen to Use HMAC
| Use Case | HMAC? |
|---|---|
| API request signing | ✓ |
| Webhook verification | ✓ |
| Session tokens | ✓ |
| Cookie signing | ✓ |
| Password storage | ✗ (use bcrypt/argon2) |
| File integrity only | ✗ (use plain hash) |
| Encryption | ✗ (HMAC doesn't encrypt) |
HMAC proves authenticity—use it when you need to verify something came from someone who knows the secret.
React to this post: