HMAC in Python — hmac.new() SHA-256 con esempi di codice

·DevOps Engineer & Python Automation Specialist·Revisionato daMaria Santos·Pubblicato

Usa il Generatore HMAC gratuito direttamente nel tuo browser — nessuna installazione.

Prova Generatore HMAC online →

Ogni callback webhook, ogni richiesta API firmata, ogni notifica di eventi Stripe o GitHub usa una firma HMAC per dimostrare che il payload non è stato manomesso. Il modulo hmac di Python gestisce HMAC-SHA256 in Python con una singola chiamata a funzione: hmac.new(key, msg, hashlib.sha256). Nessun pip install, nessuna estensione C, nessuna dipendenza da terze parti. Per verifiche rapide di firme senza scrivere codice, il generatore HMAC online ti fornisce il risultato immediatamente. Questa guida tratta hmac.new(), hmac.digest(), hmac.compare_digest(), la codifica Base64, la verifica webhook, la firma delle richieste API e ogni algoritmo hash da SHA-1 a SHA-512. Tutti gli esempi sono per Python 3.7+.

  • hmac.new(key, msg, hashlib.sha256) è il punto di ingresso standard — key e msg devono essere bytes, digestmod è obbligatorio dal Python 3.4.
  • hmac.digest(key, msg, "sha256") è un'alternativa one-shot più veloce aggiunta in Python 3.7 — restituisce bytes grezzi senza oggetto intermedio.
  • Verifica sempre le firme con hmac.compare_digest() per prevenire gli attacchi temporali — non usare mai == per il confronto HMAC.
  • Codifica in Base64 l'output grezzo di .digest() per header HTTP e firme webhook: base64.b64encode(h.digest()).
  • Il modulo hmac accetta qualsiasi algoritmo hashlib: sha1, sha256, sha384, sha512, md5, blake2b.

Cos'è HMAC?

HMAC (Hash-based Message Authentication Code) è una costruzione definita in RFC 2104 che combina una chiave segreta con una funzione hash per produrre un tag di autenticazione di dimensione fissa. A differenza di un hash semplice (che chiunque può calcolare), un HMAC richiede la conoscenza della chiave segreta. Questo significa che puoi usarlo per verificare sia l'integrità che l'autenticità di un messaggio. Se anche un singolo byte del messaggio o della chiave cambia, l'output è completamente diverso. La costruzione funziona eseguendo l'hash della chiave in XOR con due diverse costanti di padding (ipad e opad), avvolgendo il messaggio tra due operazioni hash. Il modulo hmac di Python implementa direttamente questo RFC.

Before · Python
After · Python
# Hash SHA-256 semplice — nessuna chiave segreta, chiunque può calcolarlo
hashlib.sha256(b"payment:9950:USD").hexdigest()
# "7a3b1c..."  (deterministico, pubblico)
# HMAC-SHA256 — richiede la chiave segreta per produrlo
hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest()
# "e4f2a8..."  (solo chi ha la chiave può calcolarlo)

hmac.new() — Il punto di ingresso della libreria standard

Il modulo hmac fa parte della libreria standard Python. Due import e sei pronto: import hmac, hashlib. Le tre funzioni principali sono hmac.new() (crea un oggetto HMAC), hmac.digest() (one-shot, Python 3.7+), e hmac.compare_digest() (confronto a tempo costante). Nessun pip install richiesto.

hmac.new(key, msg, digestmod) accetta tre argomenti. Sia key che msg devono essere oggetti bytes-like ( bytes, bytearray, o memoryview). L'argomento digestmod è obbligatorio dal Python 3.4 e accetta qualsiasi costruttore hashlib (come hashlib.sha256) o un nome stringa come "sha256".

Python 3.7+ — esempio minimale HMAC-SHA256
import hmac
import hashlib

key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'

# Crea l'oggetto HMAC e ottieni la firma hex
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"

L'oggetto HMAC espone due metodi di output. .digest() restituisce bytes grezzi (32 byte per SHA-256, 64 per SHA-512). .hexdigest() restituisce una stringa hex in minuscolo. La stringa hex è una semplice str Python — non serve alcun passaggio di decodifica.

Python 3.7+ — .digest() vs .hexdigest()
import hmac
import hashlib

key = b"service_auth_key"
msg = b"GET /api/v2/orders 2026-03-28T14:30:00Z"

h = hmac.new(key, msg, hashlib.sha256)

raw_bytes = h.digest()
print(type(raw_bytes), len(raw_bytes))
# <class 'bytes'> 32

hex_string = h.hexdigest()
print(type(hex_string), len(hex_string))
# <class 'str'> 64

# Rappresentano gli stessi dati — hex è solo una codifica stringa dei bytes
assert raw_bytes.hex() == hex_string

Se la chiave o il messaggio sono stringhe Python, chiama .encode() per convertirli in bytes prima di passarli a hmac.new(). Questo mette in difficoltà quasi tutti la prima volta — le stringhe Python 3 sono Unicode, non bytes, e il modulo hmac le rifiuta.

Python 3.7+ — codifica stringhe in bytes
import hmac
import hashlib

# Chiave e messaggio come stringa — .encode() converte in bytes UTF-8
api_key = "sk_live_9f3a2b7c4d8e"
request_body = '{"customer_id":"cust_4421","plan":"enterprise"}'

signature = hmac.new(
    api_key.encode(),
    request_body.encode(),
    hashlib.sha256
).hexdigest()

print(signature)
# "3a9f1b..."  — output stringa hex consistente
Nota:Il parametro digestmod non ha un valore predefinito dal Python 3.4. Chiamare hmac.new(key, msg) senza di esso genera TypeError. Prima della 3.4, il valore predefinito era MD5, ed è per questo che i manutentori di Python lo hanno rimosso — per obbligare a fare una scelta esplicita e sicura.

HMAC-SHA256 Base64, SHA-1, SHA-512 e MD5

La funzione hmac.new() funziona con qualsiasi algoritmo hash disponibile in hashlib. La maggior parte dei provider webhook e dei gateway API usa HMAC-SHA256, ma incontrerai SHA-1 in OAuth 1.0a, SHA-512 nei protocolli che lo richiedono, e MD5 in sistemi legacy non ancora aggiornati.

HMAC-SHA256 con output Base64

Molti provider webhook inviano la firma come stringa Base64 in un header HTTP. Per produrre lo stesso formato, passa i bytes grezzi di .digest() a base64.b64encode().

Python 3.7+ — codifica Base64 HMAC-SHA256
import hmac
import hashlib
import base64

key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'

# Digest grezzo → Base64 (comune per header Authorization e firme webhook)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")

print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

# Questo è il valore da confrontare con l'header X-Signature
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

HMAC-SHA1 — Compatibilità con protocolli legacy

SHA-1 è considerato debole per i nuovi progetti, ma HMAC-SHA1 è ancora richiesto da OAuth 1.0a e da alcune implementazioni webhook più vecchie. Il codice è identico — basta sostituire l'algoritmo.

Python 3.7+ — HMAC-SHA1
import hmac
import hashlib

consumer_secret = b"oauth_consumer_secret_2026"
token_secret = b"oauth_token_secret_2026"

# OAuth 1.0a usa consumer_secret&token_secret come chiave di firma
signing_key = consumer_secret + b"&" + token_secret
base_string = b"GET&https%3A%2F%2Fapi.service.com%2Fv1%2Forders&oauth_nonce%3D7f3a91bc"

sig = hmac.new(signing_key, base_string, hashlib.sha1).digest()

import base64
oauth_signature = base64.b64encode(sig).decode("ascii")
print(oauth_signature)
# "Tza3R9sE..."  — URL-encode questo per l'header Authorization

HMAC-SHA512 — Output più lungo

Python 3.7+ — HMAC-SHA512
import hmac
import hashlib

key = b"high_security_signing_key_64_bytes_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
msg = b'{"transfer_id":"xfr_9c2e","amount":500000,"currency":"EUR"}'

h = hmac.new(key, msg, hashlib.sha512)

print(len(h.digest()))   # 64 bytes (512 bit)
print(len(h.hexdigest()))  # 128 caratteri hex
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."

HMAC-MD5 — Solo per sistemi legacy

Python 3.7+ — HMAC-MD5
import hmac
import hashlib

# MD5 è crittograficamente compromesso — usalo solo per compatibilità con protocolli legacy
key = b"legacy_api_key"
msg = b"action=charge&amount=1500&merchant=store_42"

sig = hmac.new(key, msg, hashlib.md5).hexdigest()
print(sig)  # stringa hex di 32 caratteri
# "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Attenzione:HMAC-MD5 è accettabile solo per la compatibilità con sistemi che non puoi migrare. Per qualsiasi nuovo progetto, usa HMAC-SHA256 come minimo. MD5 ha note vulnerabilità di collisione che, pur essendo meno direttamente sfruttabili in modalità HMAC, lo rendono una scelta predefinita inadeguata.

Riferimento ai parametri di hmac.new()

La firma del costruttore è hmac.new(key, msg=None, digestmod). Tutte e tre le funzioni del modulo condividono lo stesso schema per gli argomenti di chiave e algoritmo.

Costruttore hmac.new()

Parametro
Tipo
Predefinito
Descrizione
key
bytes | bytearray
(obbligatorio)
La chiave segreta — deve essere bytes, non una stringa
msg
bytes | None
None
Messaggio iniziale da elaborare; altri dati possono essere aggiunti con .update()
digestmod
str | callable
(obbligatorio)
Algoritmo hash — es. hashlib.sha256 o la stringa "sha256"

hmac.digest() one-shot (Python 3.7+)

Parametro
Tipo
Descrizione
key
bytes
La chiave segreta
msg
bytes
Il messaggio da autenticare
digest
str | callable
Algoritmo hash — uguale a digestmod in hmac.new()

Il parametro digestmod accetta sia un callable (come hashlib.sha256) che un nome stringa (come "sha256"). La forma callable è preferita perché viene validata al momento dell'importazione — un errore di battitura nella forma stringa si manifesta solo a runtime.

hmac.digest() — HMAC one-shot veloce (Python 3.7+)

Python 3.7 ha aggiunto hmac.digest(key, msg, digest) come funzione a livello di modulo. Calcola l'HMAC in una singola chiamata senza creare un oggetto HMAC intermedio. Il valore di ritorno sono bytes grezzi (equivalente a chiamare .digest() sull'oggetto). Questa funzione usa un'implementazione C ottimizzata su CPython ed evita l'overhead di allocazione dell'oggetto, rendendola misurabilmente più veloce in cicli stretti.

Python 3.7+ — hmac.digest() one-shot
import hmac
import hashlib

key = b"batch_signing_key_2026"
messages = [
    b'{"order_id":"ord_001","total":4500}',
    b'{"order_id":"ord_002","total":8900}',
    b'{"order_id":"ord_003","total":2200}',
]

# Digest one-shot — nessun oggetto HMAC intermedio
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]

# Converti in hex per la visualizzazione
for msg, sig in zip(messages, signatures):
    print(f"{msg[:30]}... -> {sig.hex()[:24]}...")

Il limite: hmac.digest() restituisce solo bytes grezzi. Se hai bisogno direttamente della stringa hex, usa ancora hmac.new() per il suo metodo .hexdigest(), oppure concatena .hex() al risultato bytes.

Nota:hmac.digest() non supporta chiamate incrementali .update(). Se stai leggendo un file di grandi dimensioni a blocchi e devi calcolarne l'HMAC, usa hmac.new() e chiama .update(chunk) in un ciclo.

Verifica della firma HMAC da webhook e risposte API

L'uso più comune di HMAC in Python è la verifica delle firme webhook. Ogni provider principale (Stripe, GitHub, Shopify, Twilio) firma i payload con HMAC-SHA256 e invia la firma in un header. Lo schema è sempre lo stesso: ricalcola l'HMAC sul body grezzo della richiesta, poi confronta con hmac.compare_digest().

Verifica della firma webhook

Python 3.7+ — verifica HMAC webhook (Flask)
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = b"whsec_MbkP7x9yFqHGn3tRdWz5"

@app.route("/webhooks/payments", methods=["POST"])
def handle_payment_webhook():
    # Ottieni il body grezzo — deve corrispondere esattamente a ciò che è stato firmato
    raw_body = request.get_data()

    # Ottieni la firma dall'header
    received_sig = request.headers.get("X-Signature-256", "")

    # Ricalcola l'HMAC sul body grezzo
    expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()

    # Confronto a tempo costante — previene gli attacchi temporali
    if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
        abort(403, "Firma non valida")

    # Firma verificata — elabora l'evento
    event = request.get_json()
    print(f"Evento verificato: {event['type']} per {event['data']['amount']}")
    return "", 200

La funzione hmac.compare_digest() confronta due stringhe o sequenze di byte in tempo costante. Un confronto con == si interrompe al primo byte non corrispondente. Un attaccante può misurare il tempo di risposta su molte richieste e ricostruire gradualmente la firma attesa byte per byte. Il confronto a tempo costante elimina questo canale laterale.

Verifica dei webhook GitHub

Il formato webhook di GitHub illustra lo schema completo. Invia un header X-Hub-Signature-256 contenente sha256= seguito dall'HMAC-SHA256 in formato hex del body grezzo della richiesta, firmato con il segreto webhook configurato nelle impostazioni del repository. La differenza chiave rispetto alla verifica webhook generica è che devi rimuovere il prefisso sha256= prima del confronto, e devi leggere i bytes grezzi del body della richiesta — analizzare prima il JSON modifica la rappresentazione in bytes e rompe la verifica.

Python 3.7+ — verifica X-Hub-Signature-256 di GitHub
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
GITHUB_WEBHOOK_SECRET = b"your_github_webhook_secret"

@app.route("/webhooks/github", methods=["POST"])
def handle_github_webhook():
    # GitHub invia: X-Hub-Signature-256: sha256=<hex_digest>
    sig_header = request.headers.get("X-Hub-Signature-256", "")

    if not sig_header.startswith("sha256="):
        abort(403, "Header di firma assente o malformato")

    received_hex = sig_header[len("sha256="):]
    raw_body = request.get_data()  # bytes grezzi — non analizzare il JSON prima

    expected_hex = hmac.new(
        GITHUB_WEBHOOK_SECRET, raw_body, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected_hex, received_hex):
        abort(403, "Firma non corrispondente — il payload potrebbe essere stato manomesso")

    event_type = request.headers.get("X-GitHub-Event", "unknown")
    payload = request.get_json()
    print(f"Evento GitHub {event_type} verificato: {payload.get('action', '')}")
    return "", 200

Lo stesso schema si applica a Shopify (X-Shopify-Hmac-SHA256) e Twilio (X-Twilio-Signature), con l'unica differenza nel nome dell'header e nel fatto che la firma sia hex o Base64. Consulta sempre la documentazione del provider per confermare il formato di codifica — confondere hex e Base64 è la causa più comune degli errori di firma non corrispondente.

Autenticazione delle richieste API con HMAC

Alcune API richiedono al client di firmare ogni richiesta con HMAC. La stringa firmata di solito include il metodo HTTP, il percorso, il timestamp e il body della richiesta. Ecco uno schema che uso per l'autenticazione interna da servizio a servizio.

Python 3.7+ — firma richieste API con HMAC-SHA256
import hmac
import hashlib
import time
import json

def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
    """Genera la firma HMAC-SHA256 per una richiesta API."""
    timestamp = str(int(time.time()))

    # Costruisce la stringa di firma — metodo + percorso + timestamp + body
    signing_string = f"{method}\n{path}\n{timestamp}\n{body}"

    signature = hmac.new(
        secret,
        signing_string.encode(),
        hashlib.sha256
    ).hexdigest()

    return {
        "X-Timestamp": timestamp,
        "X-Signature": signature,
    }

# Utilizzo
api_secret = b"sk_hmac_9f3a2b7c4d8e1a6f"
body = json.dumps({"customer_id": "cust_4421", "action": "suspend_account"})

headers = sign_request(api_secret, "POST", "/api/v2/customers/actions", body)
print(headers)
# {"X-Timestamp": "1711612200", "X-Signature": "a3f1b9c0..."}

Firma delle richieste HTTP con la libreria requests

Python 3.7+ — richieste firmate con HMAC e la libreria requests
import hmac
import hashlib
import time
import json
import requests

API_SECRET = b"sk_hmac_9f3a2b7c4d8e1a6f"
BASE_URL = "https://api.billing-service.internal"

def make_signed_request(method: str, path: str, payload: dict) -> requests.Response:
    body = json.dumps(payload, separators=(",", ":"))  # JSON compatto, deterministico
    timestamp = str(int(time.time()))

    signing_string = f"{method}\n{path}\n{timestamp}\n{body}"
    signature = hmac.new(API_SECRET, signing_string.encode(), hashlib.sha256).hexdigest()

    headers = {
        "Content-Type": "application/json",
        "X-Timestamp": timestamp,
        "X-Signature": f"hmac-sha256={signature}",
    }

    try:
        return requests.request(method, f"{BASE_URL}{path}", data=body, headers=headers)
    except requests.RequestException as e:
        raise RuntimeError(f"Richiesta firmata fallita: {e}") from e

# Invia una richiesta POST firmata
resp = make_signed_request("POST", "/api/v2/invoices", {
    "customer_id": "cust_4421",
    "line_items": [
        {"description": "Piano Pro - Marzo 2026", "amount": 4900},
        {"description": "Postazioni aggiuntive (3)", "amount": 2100},
    ],
})
print(resp.status_code, resp.json())

Nota rapida: usa separators=(",", ":") quando serializzi il body per la firma. Il json.dumps() predefinito aggiunge spazi dopo i separatori, il che modifica la rappresentazione in bytes e rompe la verifica della firma se il server serializza in modo diverso. Il JSON compatto ti fornisce una forma canonica.

Generazione HMAC da riga di comando

A volte hai bisogno di calcolare un HMAC senza scrivere uno script. Il flag -c di Python e openssl gestiscono entrambi questo dal terminale.

bash — HMAC-SHA256 tramite one-liner Python
python3 -c "
import hmac, hashlib
print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest())
"
# output: stringa hex di 64 caratteri
bash — HMAC-SHA256 tramite openssl (per confronto)
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret"
# SHA2-256(stdin)= 7d11...

# Passa un file attraverso openssl HMAC
openssl dgst -sha256 -hmac "my_secret" < payload.json
bash — HMAC da variabile d'ambiente
# Memorizza la chiave in una variabile d'ambiente per evitare l'esposizione nella cronologia shell
export HMAC_KEY="sk_live_9f3a2b"
echo -n '{"event":"test"}' | python3 -c "
import hmac, hashlib, sys, os
key = os.environ['HMAC_KEY'].encode()
msg = sys.stdin.buffer.read()
print(hmac.new(key, msg, hashlib.sha256).hexdigest())
"
Nota:Il flag echo -nè fondamentale — senza di esso, echo aggiunge un carattere di newline al messaggio, il che modifica l'output HMAC. Questa è la causa più comune di firma non corrispondente durante il debug dal terminale.

Alternativa ad alte prestazioni — libreria cryptography

Per la maggior parte delle applicazioni, il modulo hmac standard è sufficientemente veloce. Se stai già usando la libreria cryptography per TLS o la gestione dei certificati, fornisce anche HMAC basato su OpenSSL. La principale differenza pratica rispetto alla stdlib è l'API .verify() basata su eccezioni descritta di seguito — genera un'eccezione in caso di mancata corrispondenza invece di restituire un booleano che potresti dimenticare di controllare.

bash — installa cryptography
pip install cryptography
Python 3.7+ — HMAC tramite la libreria cryptography
from cryptography.hazmat.primitives import hashes, hmac as crypto_hmac

key = b"webhook_signing_key_2026"
message = b'{"event":"subscription.renewed","plan":"enterprise"}'

h = crypto_hmac.HMAC(key, hashes.SHA256())
h.update(message)
signature = h.finalize()  # bytes grezzi

print(signature.hex())
# "9c4e2a..."

# Modalità verifica — genera InvalidSignature in caso di mancata corrispondenza
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature)  # genera cryptography.exceptions.InvalidSignature se errato

Il metodo .verify() della libreria cryptography è particolarmente utile: genera un'eccezione in caso di mancata corrispondenza invece di restituire un booleano. Questo rende più difficile ignorare accidentalmente un errore di verifica. La funzione hmac.compare_digest() della libreria standard restituisce True/ False, che può essere silenziosamente ignorato se lo sviluppatore dimentica di controllare il valore di ritorno.

Output nel terminale con evidenziazione della sintassi

Se stai eseguendo il debug di firme HMAC nel terminale e vuoi output colorato, rich gestisce bene questo caso.

bash — installa rich
pip install rich
Python 3.7+ — output HMAC colorato con rich
import hmac
import hashlib
from rich.console import Console
from rich.table import Table

console = Console()

key = b"debug_signing_key"
messages = {
    "/api/v2/orders": b'{"status":"active"}',
    "/api/v2/invoices": b'{"status":"pending"}',
    "/api/v2/customers": b'{"status":"verified"}',
}

table = Table(title="Firme HMAC-SHA256")
table.add_column("Endpoint", style="cyan")
table.add_column("Firma (primi 32 caratteri)", style="green")

for endpoint, body in messages.items():
    sig = hmac.new(key, body, hashlib.sha256).hexdigest()
    table.add_row(endpoint, sig[:32] + "...")

console.print(table)
Nota:Rich è solo per la visualizzazione nel terminale. Non usarlo quando scrivi firme HMAC in file, header HTTP o sistemi di aggregazione dei log — i codici di escape ANSI corromperebbero l'output.

Lavorare con file di grandi dimensioni — HMAC incrementale

Per file superiori a circa 50 MB, caricare tutto in memoria solo per calcolare un HMAC è uno spreco. Il metodo .update() sull'oggetto HMAC ti permette di passare i dati a blocchi. Questo mantiene l'utilizzo della memoria costante indipendentemente dalle dimensioni del file.

Python 3.7+ — HMAC di un file di grandi dimensioni a blocchi
import hmac
import hashlib

def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
    """Calcola HMAC-SHA256 di un file senza caricarlo interamente in memoria."""
    h = hmac.new(key, digestmod=hashlib.sha256)

    try:
        with open(filepath, "rb") as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                h.update(chunk)
    except OSError as e:
        raise OSError(f"Impossibile leggere il file '{filepath}': {e}") from e

    return h.hexdigest()

# Firma un export del database da 2 GB
signing_key = b"backup_integrity_key_2026"
signature = hmac_file(signing_key, "/var/backups/db-export-2026-03.sql.gz")
print(f"HMAC-SHA256: {signature}")
Python 3.7+ — verifica della firma HMAC di un file scaricato
import hmac
import hashlib

def verify_file_hmac(key: bytes, filepath: str, expected_hex: str) -> bool:
    """Verifica la firma HMAC-SHA256 di un file."""
    h = hmac.new(key, digestmod=hashlib.sha256)

    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)

    return hmac.compare_digest(h.hexdigest(), expected_hex)

# Verifica un artefatto scaricato
is_valid = verify_file_hmac(
    key=b"release_signing_key",
    filepath="/tmp/release-v3.2.0.tar.gz",
    expected_hex="8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f",
)
print(f"Integrità file: {'valida' if is_valid else 'CORROTTA'}")
Nota:Passa all'HMAC a blocchi quando il file supera i 50-100 MB o quando si elaborano upload in un server web dove la memoria per richiesta è importante. L'approccio con .update() usa una quantità fissa di chunk_sizedi memoria indipendentemente dalle dimensioni del file. Il valore predefinito di 64 KB è abbastanza grande da ammortizzare l'overhead delle syscall, abbastanza piccolo da restare nella cache L2 della maggior parte dell'hardware.

Generare una chiave HMAC crittograficamente sicura in Python

Una chiave debole o prevedibile compromette l'intera costruzione HMAC. Il modulo secrets (Python 3.6+) fornisce bytes casuali crittograficamente robusti. Per HMAC-SHA256, usa una chiave da 32 byte. Per HMAC-SHA512, usa 64 byte. Questi corrispondono alla dimensione del blocco interno dei rispettivi algoritmi hash, che è la lunghezza di chiave ottimale secondo RFC 2104.

Python 3.7+ — genera chiavi HMAC con secrets
import secrets

# Genera chiavi corrispondenti alla dimensione del blocco dell'algoritmo hash
sha256_key = secrets.token_bytes(32)   # 256 bit — per HMAC-SHA256
sha512_key = secrets.token_bytes(64)   # 512 bit — per HMAC-SHA512

# Rappresentazione hex — sicura per file di configurazione e variabili d'ambiente
print(f"Chiave HMAC-SHA256: {sha256_key.hex()}")
# es. "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"

print(f"Chiave HMAC-SHA512: {sha512_key.hex()}")
# stringa hex di 128 caratteri

# Base64 URL-safe — compatta, sicura per header HTTP
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Chiave Base64: {b64_key}")
# es. "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="
Attenzione:Non usare mai random.random() o random.randbytes() dal modulo random per le chiavi HMAC. Il modulo random predefinito usa il PRNG Mersenne Twister, che è prevedibile dopo aver osservato 624 output. Usa sempre secrets.token_bytes() per la casualità sensibile alla sicurezza.

Lunghezza della chiave e requisiti RFC 2104

RFC 2104 specifica che la chiave HMAC può essere di qualsiasi lunghezza, ma raccomanda una chiave di almeno L byte — dove L è la lunghezza dell'output della funzione hash sottostante. Per HMAC-SHA256 sono 32 byte (256 bit). Chiavi più corte di L bit riducono proporzionalmente il margine di sicurezza. Chiavi più lunghe della dimensione del blocco dell'hash (64 byte per SHA-256, 128 byte per SHA-512) vengono prima ridotte alla dimensione del blocco tramite hash, quindi non c'è alcun vantaggio nell'usare chiavi più lunghe della dimensione del blocco. Attieniti a 32 byte per HMAC-SHA256 e 64 byte per HMAC-SHA512.

Archiviazione sicura e rotazione delle chiavi

Non hardcodare mai le chiavi HMAC nel codice sorgente. L'approccio standard per i deployment in produzione è caricare la chiave da una variabile d'ambiente all'avvio: os.environ["HMAC_SECRET"].encode(). Per ambienti ad alta garanzia, archivia le chiavi in un sistema di gestione dei segreti come AWS Secrets Manager, HashiCorp Vault o GCP Secret Manager e recuperale a runtime. Questi sistemi forniscono log di audit, controlli di accesso e rotazione automatica senza richiedere un deploy del codice.

Pianifica la rotazione delle chiavi fin dall'inizio. Quando una chiave viene ruotata, esiste una finestra temporale in cui le richieste in volo erano firmate con la vecchia chiave e falliranno la verifica con quella nuova. La mitigazione standard è un breve periodo di sovrapposizione: accetta firme sia dalla vecchia che dalla nuova chiave per un breve periodo (minuti o ore), poi ritira la vecchia chiave. Se una chiave viene compromessa — esposta nei log, divulgata tramite un commit git o rivelata in un incidente — ruota immediatamente e considera tutte le firme prodotte con la chiave compromessa come non affidabili. Riverifica i risultati di verifica memorizzati nella cache e notifica i consumatori downstream del cambio di chiave.

Uso di bytearray e memoryview con hmac.new()

La funzione hmac.new() accetta qualsiasi oggetto bytes-like sia per la chiave che per i parametri msg. Questo significa che puoi passare bytes, bytearray o memoryview direttamente, senza copiare o convertire. Questo conta soprattutto in due scenari: implementazioni di protocollo di rete dove socket.recv_into() scrive i dati in un buffer bytearray pre-allocato, e sistemi ad alto throughput dove evitare copie intermedie riduce la pressione sul GC. Una slice memoryview è zero-copy: espone una finestra nel buffer originale senza allocare nuova memoria. A decine di migliaia di messaggi al secondo, eliminare quelle allocazioni fa una differenza misurabile in latenza e throughput.

Python 3.7+ — bytearray e memoryview con HMAC
import hmac
import hashlib

# bytearray — bytes mutabili, utili per buffer di protocollo binario
key = bytearray(b"protocol_signing_key")
frame = bytearray(b'\x01\x02\x03\x04payload_data_here')

sig = hmac.new(key, frame, hashlib.sha256).hexdigest()
print(f"Firma frame: {sig[:32]}...")

# memoryview — slice zero-copy di un buffer più grande
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"

# HMAC solo dei primi 20 byte senza copiare
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Firma sensore: {sig[:32]}...")

Errori comuni

I primi due errori li vedo in quasi ogni code review che coinvolge handler webhook. Sono facili da introdurre sotto pressione e difficili da individuare senza sapere cosa cercare.

Confrontare le firme HMAC con == invece di hmac.compare_digest()

Problema: L'operatore == si interrompe al primo byte non corrispondente, rivelando informazioni di temporizzazione che permettono a un attaccante di ricostruire incrementalmente la firma attesa.

Soluzione: Usa sempre hmac.compare_digest() per il confronto delle firme — funziona in tempo costante indipendentemente da dove si verifica la mancata corrispondenza.

Before · Python
After · Python
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if received_sig == expected_sig:  # VULNERABILE all'attacco temporale
    process_webhook(body)
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if hmac.compare_digest(received_sig, expected_sig):  # tempo costante
    process_webhook(body)
Passare una stringa invece di bytes a hmac.new()

Problema: hmac.new() richiede oggetti bytes-like. Passare una str Python genera TypeError: "key: expected bytes or bytearray, but got 'str'".

Soluzione: Chiama .encode() sulle chiavi e messaggi stringa prima di passarli a hmac.new().

Before · Python
After · Python
key = "my_api_secret"  # str, non bytes
msg = '{"event":"test"}'  # str, non bytes
sig = hmac.new(key, msg, hashlib.sha256)  # TypeError!
key = "my_api_secret"
msg = '{"event":"test"}'
sig = hmac.new(key.encode(), msg.encode(), hashlib.sha256)
Dimenticare digestmod (Python 3.4+)

Problema: Chiamare hmac.new(key, msg) senza il terzo argomento genera TypeError. Prima di Python 3.4 era predefinito su MD5, ma il valore predefinito è stato rimosso per motivi di sicurezza.

Soluzione: Specifica sempre l'algoritmo esplicitamente: hashlib.sha256, hashlib.sha512, o qualunque cosa richieda il tuo protocollo.

Before · Python
After · Python
# digestmod mancante — genera TypeError in Python 3.4+
sig = hmac.new(key, msg).hexdigest()
# Specifica sempre l'algoritmo hash
sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
Usare .hexdigest() quando il provider si aspetta Base64

Problema: Molti provider webhook (Stripe, Shopify) inviano firme in Base64, non hex. Confrontare una stringa hex con un valore Base64 fallisce sempre, causando il rifiuto di tutti i webhook.

Soluzione: Consulta la documentazione del provider per il formato della firma. Se usano Base64, codifica i bytes grezzi di .digest(), non la stringa .hexdigest().

Before · Python
After · Python
# Il provider invia Base64, ma noi calcoliamo hex — non corrisponde mai
expected = hmac.new(key, body, hashlib.sha256).hexdigest()
# "a3f1b9c0..."  vs  "o/G5wE59..."  — sempre diversi
import base64
# Corrisponde al formato del provider: bytes grezzi → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..."  — corrisponde all'header

stdlib hmac vs cryptography — Confronto rapido

Metodo
Algoritmo
Output
Streaming
Tipi personalizzati
Richiede installazione
hmac.new() + hexdigest()
Qualsiasi hashlib
Stringa hex
✓ con .update()
N/A
No (stdlib)
hmac.new() + digest()
Qualsiasi hashlib
Bytes grezzi
✓ con .update()
N/A
No (stdlib)
hmac.digest()
Qualsiasi hashlib
Bytes grezzi
✗ (one-shot)
N/A
No (stdlib, 3.7+)
hashlib.sha256 (hash semplice)
Solo SHA-256
Hex o bytes
✓ con .update()
N/A
No (stdlib)
cryptography (HMAC)
Qualsiasi
Bytes grezzi
✓ con .update()
N/A
pip install
pyca/cryptography + CMAC
AES-CMAC
Bytes grezzi
✓ con .update()
N/A
pip install

Usa il modulo hmac della stdlib per la verifica webhook, la firma API e le operazioni HMAC generali — non richiede dipendenze e copre ogni algoritmo standard. Usa hmac.digest() per operazioni batch dove la velocità one-shot è importante. Ricorri alla libreria cryptography solo quando dipendi già da essa per altre operazioni (TLS, X.509, cifratura simmetrica) e vuoi l'API .verify() basata su eccezioni. Per verifiche rapide delle firme senza scrivere Python, usa il tool HMAC Generator per incollare la chiave e il messaggio e ottenere il risultato istantaneamente.

Domande frequenti

Qual è la differenza tra hmac.new() e hmac.digest() in Python?

hmac.new() restituisce un oggetto HMAC che supporta chiamate incrementali .update() e fornisce sia .digest() (bytes grezzi) che .hexdigest() (stringa hex). hmac.digest() è una funzione one-shot aggiunta in Python 3.7 che restituisce direttamente i bytes grezzi senza creare un oggetto intermedio. Usa hmac.digest() quando hai il messaggio completo e hai solo bisogno del risultato. Usa hmac.new() quando devi passare i dati a blocchi o hai bisogno dell'output in formato hex.

Python
import hmac, hashlib

key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'

# One-shot — restituisce bytes grezzi
raw = hmac.digest(key, body, hashlib.sha256)

# Basato su oggetto — supporta aggiornamenti incrementali e output hex
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()

Come si verifica una firma HMAC in Python?

Ricalcola l'HMAC sul messaggio originale usando il segreto condiviso, poi confronta con hmac.compare_digest(). Non usare mai == per il confronto delle firme. L'operatore == è vulnerabile agli attacchi temporali perché si interrompe al primo byte non corrispondente, rivelando informazioni sulla lunghezza e il contenuto della firma attesa.

Python
import hmac, hashlib

def verify_signature(secret: bytes, message: bytes, received_sig: str) -> bool:
    expected = hmac.new(secret, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_sig)

HMAC SHA-256 in Python è uguale all'hash con hashlib.sha256?

No. hashlib.sha256 calcola un hash SHA-256 semplice dell'input, che chiunque può riprodurre. HMAC-SHA256 incorpora una chiave segreta nel calcolo dell'hash seguendo RFC 2104, quindi solo chi possiede la chiave può produrre o verificare l'output corretto. Un hash semplice dimostra l'integrità dei dati. Un HMAC dimostra sia l'integrità che l'autenticità.

Python
import hmac, hashlib

msg = b"transfer:9950:USD"
key = b"api_secret_k8x2"

plain_hash = hashlib.sha256(msg).hexdigest()  # chiunque può calcolarlo
hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest()  # richiede la chiave

Posso usare HMAC-SHA1 in Python 3?

Sì, passa hashlib.sha1 come argomento digestmod. HMAC-SHA1 funziona ancora bene in Python 3 e il modulo hmac non ha avvisi di deprecazione per esso. Detto questo, SHA-1 è considerato debole per i nuovi progetti — la sua resistenza alle collisioni è inferiore a 80 bit e il NIST lo ha deprecato per la maggior parte degli usi di firma digitale nel 2015. Il motivo principale per usare HMAC-SHA1 oggi è la compatibilità con protocolli esistenti che lo richiedono, come OAuth 1.0a o certi sistemi webhook legacy. Quando controlli entrambi i lati dell'integrazione, preferisci HMAC-SHA256 o HMAC-SHA512 per qualsiasi nuovo progetto.

Python
import hmac, hashlib

key = b"oauth_consumer_secret"
base_string = b"GET&https%3A%2F%2Fapi.example.com&oauth_nonce%3Dabc123"
sig = hmac.new(key, base_string, hashlib.sha1).digest()

Come si genera una chiave HMAC sicura in Python?

Usa secrets.token_bytes() dalla libreria standard. Per HMAC-SHA256, una chiave da 32 byte è la raccomandazione standard poiché corrisponde alla dimensione del blocco dell'hash. Per HMAC-SHA512, usa 64 byte. Non usare random.random() o os.urandom() per la generazione di chiavi nel codice applicativo — secrets è il modulo corretto per la casualità sensibile alla sicurezza a partire da Python 3.6.

Python
import secrets

hmac_sha256_key = secrets.token_bytes(32)  # 256 bit
hmac_sha512_key = secrets.token_bytes(64)  # 512 bit

# Memorizza come hex per i file di configurazione
print(hmac_sha256_key.hex())
# es. "a3f1b9c04e..."

Perché hmac.new() richiede digestmod in Python 3?

Prima di Python 3.4, digestmod aveva MD5 come valore predefinito, che è crittograficamente compromesso — MD5 ha noti attacchi di collisione e non dovrebbe mai essere usato in nuovo codice sensibile alla sicurezza. I manutentori di Python hanno rimosso il valore predefinito per forzare una scelta esplicita dell'algoritmo, impedendo agli sviluppatori di distribuire silenziosamente MAC basati su MD5. Se chiami hmac.new(key, msg) senza digestmod, ottieni un TypeError. Specifica sempre l'algoritmo esplicitamente: hashlib.sha256, hashlib.sha512, o qualsiasi altro costruttore hashlib. In caso di dubbio, hashlib.sha256 è la scelta sicura predefinita — nessuna vulnerabilità nota e abbastanza veloce per qualsiasi carico di lavoro pratico.

Python
import hmac, hashlib

key = b"secret"
msg = b"data"

# Questo genera TypeError in Python 3.4+
# hmac.new(key, msg)  # TypeError: missing required argument: 'digestmod'

# Specifica sempre l'algoritmo
h = hmac.new(key, msg, hashlib.sha256)

Per una verifica HMAC rapida senza avviare uno script Python, incolla la tua chiave e il messaggio nel generatore HMAC online — supporta SHA-256, SHA-384 e SHA-512 con risultati istantanei.

Strumenti correlati

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.

MS
Maria SantosRevisore tecnico

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.