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):
    pass

Different 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 None
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 None

Digest 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 chars

Copy 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())  # Different

When to Use HMAC

Use CaseHMAC?
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: