Python HMAC: Authenticate Messages

Published on: August 19, 2026
Reading time: 5 minutes
Account verification screen representing message authentication with Python HMAC

HMAC is a cryptographic construction that combines a secret key with a hash function to produce a message authentication code. Unlike an ordinary hash, only a party that knows the key can reproduce the correct value. A valid HMAC therefore indicates that the message was not modified and that it was created by someone holding the shared secret.

Python’s hmac module implements the algorithm standardized in RFC 2104. It is widely used to validate webhooks, authenticate messages between services, protect distributed configuration, sign compact cookies, and verify payloads received from queues or APIs. This guide covers generation, safe verification, canonical messages, replay attacks, and key rotation.

Regular hashes versus HMAC

A digest such as SHA-256 has no secret. Anyone who changes a message can calculate a new digest. A trusted checksum can detect file corruption, but a digest alone does not authenticate the sender.

HMAC incorporates a key through a standardized construction. Do not invent alternatives such as sha256(secret + message). Custom combinations can introduce length-extension issues, ambiguous fields, or unsafe key handling.

Review the Python hashlib guide for general hashes and the password hashing guide for credential storage. HMAC solves a different problem: message authentication.

Creating HMAC-SHA256

import hmac
import hashlib

key = b"randomly-generated-server-secret"
message = b"order=123&amount=49.90"

mac = hmac.new(key, message, digestmod=hashlib.sha256)
print(mac.hexdigest())

The key and message must be bytes-like objects. Encode text explicitly:

key = "server secret".encode("utf-8")
message = "action=confirm".encode("utf-8")

digestmod is required. SHA-256 is a common interoperable choice. HMAC requires a fixed-size digest, so extendable-output functions such as SHAKE-128 and SHAKE-256 cannot be used.

The hmac.digest() shortcut

When the complete message already fits in memory, hmac.digest() provides a compact call and may use an optimized implementation.

signature = hmac.digest(key, message, "sha256")
print(signature.hex())

For streams and large files, create an HMAC object and update it incrementally.

Incremental processing

from pathlib import Path
import hmac
import hashlib

def hmac_file(path: Path, key: bytes) -> str:
    mac = hmac.new(key, digestmod=hashlib.sha256)
    with path.open("rb") as file:
        while chunk := file.read(1024 * 1024):
            mac.update(chunk)
    return mac.hexdigest()

This avoids loading a large file into memory and follows the approach described in reading giant files without freezing Python.

Verification with compare_digest()

Do not compare secret authentication tags with ==. A typical comparison may stop at the first mismatch, creating small timing differences. Repeated measurements can sometimes reveal information.

def verify_signature(
    key: bytes,
    message: bytes,
    supplied_hex: str,
) -> bool:
    expected = hmac.new(
        key,
        message,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(expected, supplied_hex)

The two values should have the same type. Text comparisons must use ASCII-only strings, such as hexadecimal digests. Length differences may still reveal length information, so validate the expected format before comparing.

Validating a webhook

A webhook normally sends a raw request body and a signature header. Calculate the HMAC over exactly the bytes specified by the provider.

def verify_webhook(body: bytes, header: str, secret: bytes) -> None:
    if not header.startswith("sha256="):
        raise ValueError("Invalid signature format")

    supplied = header.removeprefix("sha256=")
    if len(supplied) != 64:
        raise ValueError("Invalid signature length")

    expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, supplied):
        raise PermissionError("Invalid signature")

Do not parse JSON and serialize it again before verification. Whitespace, key ordering, escapes, and line endings can change the bytes. Read the raw body, verify the signature, and only then parse the payload.

Canonical message design

When you define a signing protocol, specify fields, order, encoding, and separators. Naive concatenation can be ambiguous: ("ab", "c") and ("a", "bc") produce the same bytes if no boundary exists.

def canonical_message(timestamp: int, method: str, path: str, body: bytes) -> bytes:
    prefix = f"v1\n{timestamp}\n{method.upper()}\n{path}\n".encode("utf-8")
    return prefix + body

Include a version marker so the protocol can evolve without confusing new and legacy signatures.

Replay protection

A valid HMAC does not stop an attacker from capturing and resending the same valid request. Sensitive commands should authenticate a timestamp, nonce, or unique event ID.

import time

MAX_AGE = 300
if abs(time.time() - timestamp) > MAX_AGE:
    raise PermissionError("Expired message")

Store accepted nonces for the permitted window. A safe order is: validate structure and limits, check timestamp, calculate HMAC, compare, record the nonce, and only then perform the action.

Key generation and storage

An HMAC key needs sufficient entropy and should come from a cryptographically secure source.

import secrets
key = secrets.token_bytes(32)

Do not derive the key from a short phrase, commit it to source control, or print it in logs. Use a secret manager, protected environment variable, or restricted file. Separate keys by purpose and environment.

Key rotation

Associate each key with a public identifier such as key_id. During rotation, sign only with the new key while temporarily accepting the previous key for messages created before the change.

KEYS = {
    "2026-08": b"new-key...",
    "2026-07": b"previous-key...",
}

Validate and limit the identifier. Do not blindly try hundreds of keys for attacker-controlled requests.

Tag truncation

Some protocols transmit only part of an HMAC to save space. Truncation lowers forgery resistance. Prefer the complete digest unless a reviewed standard specifies an acceptable minimum. Never accept arbitrary-length prefixes.

Signing API requests

API signing schemes often authenticate the HTTP method, canonical path, sorted query, timestamp, and a hash of the body. Client and server must normalize all elements identically. The guide to consuming REST APIs in Python complements this with timeout and response validation practices.

HMAC does not encrypt

The message remains readable. HMAC provides integrity and authenticity, not confidentiality. Use TLS for transport and authenticated encryption for secret stored data. Do not reuse one key for HMAC and encryption; derive purpose-specific keys with an appropriate KDF.

Common mistakes

Frequent errors include using ==, signing reserialized JSON, omitting a timestamp, accepting variable-length tags, using predictable keys, logging secrets, reusing a key for unrelated purposes, and executing the business action before verification.

Best practices

Use HMAC-SHA256, a random key of at least 32 bytes, a versioned canonical message, and compare_digest(). Authenticate timestamp and nonce, apply size limits before processing, rotate keys by identifier, and log only non-sensitive metadata. Prefer the provider’s official verification library when its signing format is complex.

Conclusion

Python’s hmac module provides a standard way to authenticate messages with a secret key and a hash function. The call itself is simple; security depends on the exact bytes signed, safe comparison, key management, and replay protection. When these details are specified clearly, HMAC is a robust choice for webhooks and service-to-service communication.

See the official hmac documentation and RFC 2104.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Fingerprint scanner representing digest verification with Python hashlib
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python hashlib: Secure Hashes

    Learn Python hashlib for SHA-256, file verification, BLAKE2, key derivation, password hashing decisions, and safer digest handling.

    Ler mais

    Tempo de leitura: 5 minutos
    19/08/2026
    Projected binary code representing Python binascii conversions
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python binascii: Binary and ASCII

    Learn Python binascii to convert hexadecimal, Base64 and quoted-printable, calculate CRC values, and validate binary data safely.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python codecs: Master Encodings

    Learn Python codecs to inspect encodings, handle errors and BOMs, process incremental streams, and migrate codecs.open to open.

    Ler mais

    Tempo de leitura: 6 minutos
    18/08/2026
    A person in a hoodie coding on dual monitors, depicting cybersecurity and hacking themes.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python base64: Encode Binary Data

    Learn Python base64 to encode bytes, use URL-safe Base64, validate padding, enforce limits, and distinguish encoding from encryption.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    Close-up of a woman gently holding a Burmese python, showcasing exotic pet care.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python urllib.parse: Handle URLs

    Learn Python urllib.parse to split URLs, build queries, encode components, and avoid risks involving urljoin, redirects, logs, and SSRF.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python ipaddress: IPv4 and IPv6

    Learn Python ipaddress to validate IPv4 and IPv6, calculate CIDR networks, split subnets, summarize ranges, and build safer access policies.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026