SHA-256 in Python — Guida hashlib con esempi

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

Usa il Generatore Hash SHA-256 gratuito direttamente nel tuo browser — nessuna installazione.

Prova Generatore Hash SHA-256 online →

Ogni pipeline di deployment che ho costruito ha prima o poi bisogno di verificare il checksum di un file, firmare il payload di un webhook o ricavare una chiave di cache. L'hashing SHA-256 in Python con il modulo integrato hashlib gestisce tutti questi casi — ed è già disponibile nell'installazione standard. hashlib.sha256() delega all'implementazione C di OpenSSL su CPython, quindi è veloce e conforme FIPS out of the box. Per un hash rapido senza scrivere codice, il generatore SHA-256 online fornisce il risultato all'istante. Tutti gli esempi sono per Python 3.9+.

  • hashlib.sha256(data).hexdigest() è il metodo standard per calcolare l'hash di bytes — parte della stdlib, basato su OpenSSL.
  • Le stringhe devono essere prima codificate in bytes: hashlib.sha256("testo".encode("utf-8")).
  • Per i checksum di file, si alimentano blocchi tramite .update() — non caricare mai un file di grandi dimensioni interamente in memoria.
  • HMAC-SHA256 richiede il modulo hmac: hmac.new(key, msg, hashlib.sha256) — SHA-256 da solo non ha una chiave.

Cos'è l'hashing SHA-256?

SHA-256 (Secure Hash Algorithm, 256 bit) prende un input di lunghezza arbitraria e produce un digest fisso di 256 bit (32 byte). Lo stesso input produce sempre lo stesso output, ma anche un singolo bit di differenza nell'input genera un hash completamente diverso — questa proprietà si chiama effetto valanga. SHA-256 fa parte della famiglia SHA-2, standardizzata dal NIST, ed è alla base delle impronte dei certificati TLS, degli ID di commit Git, delle intestazioni dei blocchi Bitcoin e della verifica dell'integrità dei file. L'algoritmo usa una costruzione Merkle-Damgård con 64 round di compressione per produrre il suo output di 256 bit.

Before · text
After · text
deployment-v4.2.1
a1f7c3d8e9b2...27ae41e4649b (64 caratteri hex)

Il digest esadecimale sopra è la rappresentazione standard — 64 caratteri esadecimali, sempre della stessa lunghezza indipendentemente dal fatto che si stia calcolando l'hash di un singolo byte o di un'intera immagine disco.

hashlib.sha256() — L'approccio della libreria standard

Il modulo hashlib è incluso in ogni installazione Python — nessun pip install necessario. Si chiama hashlib.sha256() con un argomento di tipo bytes per creare un oggetto hash, quindi si ottiene il risultato con .hexdigest() (stringa esadecimale) o .digest() (byte grezzi). Il nome della funzione è in minuscolo: sha256, non SHA256.

Python 3.9+ — hash SHA-256 minimale
import hashlib

# Hash diretto di una stringa di byte
digest = hashlib.sha256(b"deployment-v4.2.1").hexdigest()
print(digest)
# a8f5f167f44f4964e6c998dee827110c3f1de4d0280c68cba98cf70b4b5157db

L'errore più comune con hashlib.sha256() è passare una str invece di bytes. Le stringhe Python sono Unicode e le funzioni hash operano su byte grezzi. È necessario chiamare .encode("utf-8") prima dell'hashing. Questo è l'errore che fa inciampare quasi tutti la prima volta.

Python 3.9+ — hash di una stringa
import hashlib

# Le stringhe devono essere codificate in bytes prima dell'hashing
config_key = "redis://cache.internal:6379/0"
digest = hashlib.sha256(config_key.encode("utf-8")).hexdigest()
print(digest)
# 7d3f8c2a1b9e4f5d6c8a7b3e2f1d9c4a5b8e7f6d3c2a1b9e4f5d6c8a7b3e2f1d

Il metodo .update() permette di alimentare dati in modo incrementale. Chiamare h.update(a); h.update(b) è equivalente a hashlib.sha256(a + b). Questo è il modo per calcolare l'hash di file a blocchi senza caricare l'intero contenuto in memoria.

Python 3.9+ — hashing incrementale con update()
import hashlib

h = hashlib.sha256()
h.update(b"request_id=req_7f3a91bc")
h.update(b"&timestamp=1741614120")
h.update(b"&amount=4999")
print(h.hexdigest())
# Equivalente a hashlib.sha256(b"request_id=req_7f3a91bc&timestamp=1741614120&amount=4999").hexdigest()
Nota:.digest() restituisce i 32 byte grezzi. .hexdigest() restituisce una stringa esadecimale di 64 caratteri. Usare .digest() quando si alimenta il risultato a HMAC, codifica Base64 o protocolli binari. Usare .hexdigest() per log, colonne di database e confronto di checksum.

HMAC-SHA256 — Hashing con chiave tramite il modulo hmac

SHA-256 da solo non prevede una chiave segreta — chiunque abbia lo stesso input può calcolare lo stesso hash. Se si ha bisogno di dimostrare che un messaggio proviene da un mittente specifico (verifica webhook, firma di richieste API, autenticazione token), è necessario HMAC. Il modulo hmac fa parte della libreria standard di Python e incorpora la chiave nel processo di hashing in modo che solo chi possiede la chiave possa produrre o verificare lo stesso digest.

Python 3.9+ — HMAC-SHA256 di base
import hmac
import hashlib

# Verifica della firma webhook
secret_key = b"whsec_9f3a7b2e1d4c8a5b"
payload = b'{"event":"invoice.paid","invoice_id":"inv_8d2c","amount":14900}'

signature = hmac.new(secret_key, payload, hashlib.sha256).hexdigest()
print(signature)
# Digest HMAC-SHA256 esadecimale di 64 caratteri

Per verificare un HMAC in arrivo è necessario usare hmac.compare_digest() invece dell'operatore ==. L'operatore di uguaglianza è vulnerabile ad attacchi temporali — si interrompe al primo byte diverso e un attaccante può misurare i tempi di risposta per indovinare la firma corretta byte per byte. compare_digest() viene eseguito in tempo costante indipendentemente da dove si verifica la discrepanza.

Python 3.9+ — verifica di una firma webhook
import hmac
import hashlib

def verify_webhook(payload: bytes, received_sig: str, secret: bytes) -> bool:
    """Verifica una firma webhook usando il confronto in tempo costante."""
    expected = hmac.new(secret, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_sig)

# Simulazione di una verifica webhook in stile Stripe
incoming_payload = b'{"event":"payment.completed","amount":4999}'
incoming_signature = "a1b2c3d4e5f6..."  # dall'header X-Signature
webhook_secret = b"whsec_9f3a7b2e1d4c"

if verify_webhook(incoming_payload, incoming_signature, webhook_secret):
    print("Firma valida — elabora l'evento")
else:
    print("Firma non corrispondente — rifiuta la richiesta")

Firma delle richieste API con HMAC-SHA256

La firma delle richieste API segue lo stesso principio: si costruisce una stringa canonica dalle componenti della richiesta (metodo, percorso, timestamp, hash del corpo) e la si firma con la propria chiave segreta. AWS Signature V4, Stripe e i webhook GitHub usano tutti varianti di questo schema.

Python 3.9+ — firma di una richiesta API con HMAC-SHA256
import hmac
import hashlib
import time

def sign_request(method: str, path: str, body: bytes, secret: bytes) -> str:
    """Crea una firma HMAC-SHA256 per una richiesta API."""
    timestamp = str(int(time.time()))
    body_hash = hashlib.sha256(body).hexdigest()

    # Stringa canonica: metodo + percorso + timestamp + hash del corpo
    canonical = f"{method}\n{path}\n{timestamp}\n{body_hash}"
    signature = hmac.new(secret, canonical.encode("utf-8"), hashlib.sha256).hexdigest()

    return f"ts={timestamp},sig={signature}"

# Utilizzo
api_secret = b"sk_live_9f3a7b2e1d4c8a5b6e7f"
request_body = b'{"customer_id":"cust_4f2a","plan":"enterprise"}'
auth_header = sign_request("POST", "/api/v2/subscriptions", request_body, api_secret)
print(f"Authorization: HMAC-SHA256 {auth_header}")
# Authorization: HMAC-SHA256 ts=1741614120,sig=7d3f8c2a...

HMAC-SHA256 codificato in Base64

Alcune API (AWS Signature V4, vari gateway di pagamento) si aspettano il risultato HMAC come stringa Base64 anziché esadecimale. La differenza: il formato hex usa 64 caratteri, Base64 ne usa 44 per lo stesso digest di 32 byte.

Python 3.9+ — HMAC-SHA256 codificato in Base64
import hmac
import hashlib
import base64

secret = b"webhook_secret_9f3a"
message = b"POST /api/v2/events 1741614120"

# Output esadecimale: 64 caratteri
hex_sig = hmac.new(secret, message, hashlib.sha256).hexdigest()
print(f"Hex:    {hex_sig}")

# Output Base64: 44 caratteri (più corto, comune negli header HTTP)
raw_sig = hmac.new(secret, message, hashlib.sha256).digest()
b64_sig = base64.b64encode(raw_sig).decode("ascii")
print(f"Base64: {b64_sig}")

Hashing di datetime, UUID e oggetti personalizzati

SHA-256 opera su byte grezzi, quindi i tipi non-bytes — datetime, UUID, dataclass, modelli Pydantic — devono essere serializzati in bytes prima dell'hashing. Non esiste conversione automatica; si sceglie la rappresentazione canonica. Per un hashing deterministico tra sistemi diversi, usare sempre una codifica esplicita e un formato di serializzazione stabile (ISO 8601 per i datetime, la forma stringa standard per gli UUID, JSON con chiavi ordinate per i dizionari).

Python 3.9+ — hash di datetime e UUID
import hashlib
import uuid
from datetime import datetime, timezone

# datetime — usare ISO 8601 con offset UTC esplicito per portabilità
event_time = datetime(2026, 3, 28, 12, 0, 0, tzinfo=timezone.utc)
time_hash = hashlib.sha256(event_time.isoformat().encode("utf-8")).hexdigest()
print(f"hash datetime: {time_hash[:16]}...")

# UUID — hash della forma stringa canonica (minuscola, con trattini)
record_id = uuid.uuid4()
uuid_hash = hashlib.sha256(str(record_id).encode("utf-8")).hexdigest()
print(f"hash UUID: {uuid_hash[:16]}...")

Per oggetti personalizzati, serializzare in una rappresentazione canonica di bytes prima dell'hashing. JSON con chiavi ordinate funziona bene per oggetti simili a dizionari:

Python 3.9+ — hash di un oggetto personalizzato
import hashlib
import json
from dataclasses import dataclass, asdict

@dataclass
class Evento:
    id: str
    type: str
    amount: int
    timestamp: str

def hash_evento(evento: Evento) -> str:
    """Hash di un'istanza dataclass usando JSON con chiavi ordinate per il determinismo."""
    canonical = json.dumps(asdict(evento), sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()

e = Evento(id="evt_4f2a", type="payment.completed", amount=4999, timestamp="2026-03-28T12:00:00Z")
print(hash_evento(e))  # stabile tra esecuzioni e macchine diverse
Nota:Ordinare sempre le chiavi del dizionario (sort_keys=True) quando si calcola l'hash di oggetti serializzati in JSON. L'ordine di inserimento nei dizionari è preservato in Python 3.7+, ma può differire tra percorsi di serializzazione diversi, producendo hash diversi per dati identici.

Checksum SHA-256 dei file — Verifica di download e artefatti

Il calcolo del checksum SHA-256 di un file è uno degli usi più comuni dell'algoritmo. Lo si incontra ovunque: pagine di release per i binari Go, wheel Python, manifest di immagini Docker, aggiornamenti firmware. Il punto chiave è leggere il file a blocchi anziché caricarlo tutto in una volta — un'immagine ISO da 2 GB non dovrebbe richiedere 2 GB di RAM solo per calcolarne l'hash.

Python 3.9+ — checksum SHA-256 di un file (a blocchi)
import hashlib

def sha256_checksum(filepath: str, chunk_size: int = 8192) -> str:
    """Calcola l'hash SHA-256 di un file leggendo a blocchi per risparmiare memoria."""
    h = hashlib.sha256()
    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(chunk_size), b""):
            h.update(chunk)
    return h.hexdigest()

# Hash di un artefatto di release
checksum = sha256_checksum("/tmp/release-v4.2.1.tar.gz")
print(f"SHA-256: {checksum}")

Python 3.11 ha introdotto hashlib.file_digest(), che gestisce internamente la lettura a blocchi e può usare ottimizzazioni zero-copy sulle piattaforme supportate. Se si utilizza la versione 3.11 o successiva, preferire questa alla lettura manuale a ciclo.

Python 3.11+ — hashlib.file_digest()
import hashlib

with open("/tmp/release-v4.2.1.tar.gz", "rb") as f:
    digest = hashlib.file_digest(f, "sha256")

print(digest.hexdigest())

Verifica di un file scaricato confrontandolo con un checksum noto

Python 3.9+ — verifica del checksum
import hashlib
import hmac as hmac_mod  # solo per compare_digest

def verify_checksum(filepath: str, expected_hex: str) -> bool:
    """Verifica il checksum SHA-256 usando il confronto in tempo costante."""
    h = hashlib.sha256()
    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            h.update(chunk)
    return hmac_mod.compare_digest(h.hexdigest(), expected_hex.lower())

# Verifica di un artefatto di release
expected = "a8f5f167f44f4964e6c998dee827110c3f1de4d0280c68cba98cf70b4b5157db"
if verify_checksum("/tmp/release-v4.2.1.tar.gz", expected):
    print("Checksum corrispondente — file integro")
else:
    print("Checksum non corrispondente — il file potrebbe essere corrotto o manomesso")
Nota:Usare sempre hmac.compare_digest() per il confronto dei checksum, anche quando non è coinvolta una chiave segreta. Il confronto in tempo costante previene fughe di informazioni basate sui tempi. L'operatore == funziona dal punto di vista logico, ma non è sicuro nei contesti sensibili alla sicurezza.

SHA-256 con codifica Base64

Alcuni protocolli si aspettano il digest SHA-256 come stringa Base64 anziché esadecimale. Gli header HTTP come Content-Digest e Integrity (Subresource Integrity nei browser) usano Base64, e le firme JWT sono codificate in Base64url. Il punto cruciale è codificare in Base64 i byte grezzi di .digest(), non la stringa esadecimale.

Python 3.9+ — SHA-256 codificato in Base64
import hashlib
import base64

data = b"integrity check payload"

# Corretto: Base64 dei byte grezzi (32 byte → 44 caratteri Base64)
raw_digest = hashlib.sha256(data).digest()
b64_digest = base64.b64encode(raw_digest).decode("ascii")
print(f"sha256-{b64_digest}")
# sha256-<44 caratteri>

# Sbagliato: Base64 della stringa esadecimale (64 byte ASCII → 88 caratteri Base64 — il doppio)
hex_digest = hashlib.sha256(data).hexdigest()
wrong = base64.b64encode(hex_digest.encode()).decode()
print(f"Lunghezza errata: {len(wrong)} caratteri")  # 88 — non quello che si aspettano le API
Attenzione:Codificare in Base64 la stringa esadecimale invece dei byte grezzi è un errore comune che produce un output doppio rispetto alla lunghezza attesa. Le API lo rifiutano e il messaggio di errore di solito non fornisce indizi sul perché. Chiamare sempre .digest(), non .hexdigest(), prima della codifica Base64.

Riferimento hashlib.sha256()

Il costruttore e i metodi di un oggetto hash SHA-256:

Parametro / Metodo
Tipo
Descrizione
data (posizionale)
bytes
Dati iniziali da sottoporre a hash — equivale a chiamare update(data) subito dopo la costruzione
.update(data)
bytes
Aggiunge ulteriori byte allo stato dell'hash — può essere chiamato più volte per input a blocchi
.digest()
→ bytes
Restituisce il digest binario grezzo di 32 byte — da usare per input HMAC, protocolli binari, codifica Base64
.hexdigest()
→ str
Restituisce la stringa esadecimale minuscola di 64 caratteri — la rappresentazione standard per checksum e verifica
.copy()
→ hash object
Restituisce un clone dello stato hash corrente — permette di ramificare un digest senza ricalcolare dall'inizio
hashlib.sha256()
costruttore
Crea un nuovo oggetto hash SHA-256 basato su OpenSSL su CPython — usedfips=True su 3.9+ limita agli algoritmi approvati FIPS

Parametri di hmac.new() per l'hashing con chiave:

Parametro
Tipo
Descrizione
key
bytes
La chiave segreta — deve essere bytes, non str
msg
bytes | None
Messaggio iniziale da autenticare — None di default, si può chiamare update() in seguito
digestmod
str | callable
Algoritmo hash: passare hashlib.sha256 o la stringa "sha256"

La libreria cryptography — Un'API SHA-256 alternativa

Il pacchetto cryptography offre un'API diversa per SHA-256 tramite le sue primitive hazmat. Raramente la utilizzo quando ho bisogno solo di un hash — hashlib è più semplice e non ha dipendenze esterne. Ma se il progetto dipende già da cryptography per TLS, X.509 o cifratura simmetrica, usare la sua API per gli hash mantiene tutto sotto un'unica libreria e garantisce una gestione coerente degli errori.

Python 3.9+ — SHA-256 con la libreria cryptography
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend

digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(b"deployment-v4.2.1")
result = digest.finalize()  # 32 byte grezzi

print(result.hex())  # stringa hex di 64 caratteri
# a8f5f167f44f4964e6c998dee827110c3f1de4d0280c68cba98cf70b4b5157db
Attenzione:La libreria cryptography richiede pip install cryptography. L'oggetto hash è monouso: chiamare .finalize() una seconda volta genera AlreadyFinalized. Usare .copy() prima di finalizzare se si ha bisogno di ramificare lo stato hash.

Hash di dati da file e risposta API

Due scenari ricorrono continuamente: calcolare l'hash di un file su disco per verificare un artefatto di release, e calcolare l'hash del corpo di una risposta HTTP per usarlo come chiave di cache o per verificare un webhook.

Leggi file → Calcola SHA-256 → Confronta

Python 3.9+ — hash di un backup di configurazione con gestione degli errori
import hashlib
import sys

def hash_file_safe(filepath: str) -> str | None:
    """Calcola l'hash di un file con corretta gestione degli errori."""
    try:
        h = hashlib.sha256()
        with open(filepath, "rb") as f:
            for chunk in iter(lambda: f.read(16384), b""):
                h.update(chunk)
        return h.hexdigest()
    except FileNotFoundError:
        print(f"Errore: {filepath} non trovato", file=sys.stderr)
        return None
    except PermissionError:
        print(f"Errore: permesso di lettura negato per {filepath}", file=sys.stderr)
        return None

result = hash_file_safe("/etc/nginx/nginx.conf")
if result:
    print(f"SHA-256: {result}")

Risposta HTTP → Hash del corpo come chiave di cache

Python 3.9+ — hash di una risposta API
import hashlib
import urllib.request
import json

def fetch_and_hash(url: str) -> tuple[dict, str]:
    """Recupera JSON da un'API e restituisce sia i dati che il loro hash SHA-256."""
    try:
        with urllib.request.urlopen(url, timeout=10) as resp:
            body = resp.read()
            content_hash = hashlib.sha256(body).hexdigest()
            data = json.loads(body)
            return data, content_hash
    except urllib.error.URLError as exc:
        raise ConnectionError(f"Impossibile raggiungere {url}: {exc}") from exc

# Chiave di cache basata sul contenuto della risposta
data, digest = fetch_and_hash("https://api.exchange.internal/v2/rates")
print(f"Hash risposta: {digest[:16]}...")
print(f"EUR/USD: {data.get('rates', {}).get('EUR', 'N/A')}")

Per un controllo rapido, il generatore SHA-256 di ToolDeck funziona interamente nel browser — nessun codice necessario.

Hashing SHA-256 da riga di comando

A volte serve un hash rapido nel terminale durante un incident o un deploy. Il modulo hashlib di Python non ha un sottocomando CLI integrato (a differenza di python3 -m json.tool), ma si può usare un one-liner o i tool di sistema.

bash — hash di una stringa dalla riga di comando
# One-liner Python
echo -n "deployment-v4.2.1" | python3 -c "import hashlib,sys; print(hashlib.sha256(sys.stdin.buffer.read()).hexdigest())"

# macOS / BSD
echo -n "deployment-v4.2.1" | shasum -a 256

# Linux (coreutils)
echo -n "deployment-v4.2.1" | sha256sum

# OpenSSL (multi-piattaforma)
echo -n "deployment-v4.2.1" | openssl dgst -sha256
bash — hash di un file
# Hash di un tarball di release
sha256sum release-v4.2.1.tar.gz
# oppure
openssl dgst -sha256 release-v4.2.1.tar.gz

# Verifica contro un checksum noto
echo "a8f5f167f44f4964e6c998dee827110c release-v4.2.1.tar.gz" | sha256sum -c -
# release-v4.2.1.tar.gz: OK
Nota:Usare sempre echo -n(senza newline finale) quando si calcola l'hash di stringhe dalla riga di comando. Un semplice echo aggiunge \n, che cambia l'hash. Questa è la causa principale per cui si ottengono hash diversi tra Python e la shell.

Alternativa ad alte prestazioni — hashlib con OpenSSL e pycryptodome

Su CPython, hashlib.sha256() delega già all'implementazione C di OpenSSL, quindi è veloce — tipicamente 500+ MB/s su hardware moderno.

Se l'hashing SHA-256 emerge nel profiler — ad esempio si stanno calcolando checksum per migliaia di file in una pipeline CI o si sta calcolando l'hash di ogni corpo di richiesta in un gateway API ad alto traffico — esistono due opzioni: ottimizzare il pattern di chiamata a hashlib, oppure passare a pycryptodome per un'API crittografica unificata che copre anche SHA-3 e BLAKE2:

bash — installazione di pycryptodome
pip install pycryptodome
Python 3.9+ — SHA-256 con pycryptodome
from Crypto.Hash import SHA256

h = SHA256.new()
h.update(b"deployment-v4.2.1")
print(h.hexdigest())
# a8f5f167f44f4964e6c998dee827110c3f1de4d0280c68cba98cf70b4b5157db

Per l'hashing parallelo di file ad alto volume, i guadagni maggiori si ottengono riducendo l'overhead Python attraverso blocchi più grandi e il threading:

Python 3.9+ — hashing batch di file con hashlib
import hashlib
import os
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

def hash_file(path: Path) -> tuple[str, str]:
    """Calcola l'hash di un singolo file e restituisce (percorso, digest esadecimale)."""
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):  # blocchi da 64 KB
            h.update(chunk)
    return str(path), h.hexdigest()

def hash_directory(directory: str, pattern: str = "*.tar.gz") -> dict[str, str]:
    """Calcola l'hash di tutti i file corrispondenti in parallelo usando i thread."""
    files = list(Path(directory).glob(pattern))
    results = {}
    with ThreadPoolExecutor(max_workers=os.cpu_count()) as pool:
        for path, digest in pool.map(hash_file, files):
            results[path] = digest
    return results

# Hash di tutti gli artefatti di release in parallelo
checksums = hash_directory("/var/releases", "*.tar.gz")
for path, digest in checksums.items():
    print(f"{digest}  {path}")

Usare blocchi da 64 KB invece di 8 KB riduce di 8 volte il numero di chiamate da Python a C. I thread funzionano bene qui perché il GIL viene rilasciato durante l'hashing a livello C — il collo di bottiglia è l'I/O su disco, non la CPU.

Output nel terminale con evidenziazione della sintassi

La libreria rich è utile quando si vuole verificare un batch di file e si desidera una tabella con lo stato pass/fail per ogni file, anziché un flusso di stringhe esadecimali non elaborate.

bash — installazione di rich
pip install rich
Python 3.9+ — output rich per la verifica degli hash
import hashlib
from pathlib import Path
from rich.console import Console
from rich.table import Table

console = Console()

def hash_and_display(files: list[str], expected: dict[str, str]) -> None:
    """Calcola l'hash dei file e mostra i risultati con verifica a colori."""
    table = Table(title="Verifica SHA-256")
    table.add_column("File", style="cyan")
    table.add_column("SHA-256", style="dim", max_width=20)
    table.add_column("Stato")

    for filepath in files:
        h = hashlib.sha256()
        with open(filepath, "rb") as f:
            for chunk in iter(lambda: f.read(8192), b""):
                h.update(chunk)
        digest = h.hexdigest()

        name = Path(filepath).name
        status = "[green]✓ OK[/green]" if expected.get(name) == digest else "[red]✗ MISMATCH[/red]"
        table.add_row(name, f"{digest[:16]}...", status)

    console.print(table)

# Utilizzo
expected_checksums = {
    "api-gateway-v3.1.tar.gz": "a8f5f167f44f4964...",
    "worker-v3.1.tar.gz": "7d3f8c2a1b9e4f5d...",
}
hash_and_display(
    ["/var/releases/api-gateway-v3.1.tar.gz", "/var/releases/worker-v3.1.tar.gz"],
    expected_checksums,
)
Nota:L'output di rich è solo per la visualizzazione nel terminale. Non scrivere codici di escape ANSI nei file di log o nelle risposte API — rimuoverli con console.print(data, highlight=False) o reindirizzare su file con Console(file=open(...)).

Lavoro con file di grandi dimensioni

Il pattern a blocchi con .update() gestisce file di qualsiasi dimensione con utilizzo costante della memoria. Per file molto grandi (immagini disco multi-GB, backup di database), la preoccupazione principale si sposta dalla memoria al feedback per l'utente — calcolare l'hash di 10 GB a 500 MB/s richiede comunque 20 secondi, e il silenzio durante quel tempo mette a disagio.

Python 3.9+ — hash di file di grandi dimensioni con indicatore di avanzamento
import hashlib
import os

def sha256_with_progress(filepath: str) -> str:
    """Calcola l'hash di un file di grandi dimensioni con indicatore di avanzamento su stderr."""
    file_size = os.path.getsize(filepath)
    h = hashlib.sha256()
    bytes_read = 0

    with open(filepath, "rb") as f:
        while chunk := f.read(1 << 20):  # blocchi da 1 MB
            h.update(chunk)
            bytes_read += len(chunk)
            pct = (bytes_read / file_size) * 100
            print(f"\r  Hashing: {pct:.1f}% ({bytes_read >> 20} MB / {file_size >> 20} MB)",
                  end="", flush=True)

    print()  # nuova riga dopo il progresso
    return h.hexdigest()

digest = sha256_with_progress("/mnt/backups/db-snapshot-2026-03.sql.gz")
print(f"SHA-256: {digest}")

NDJSON / JSON Lines — Hash di ogni record separatamente

Python 3.9+ — hash dei singoli record in uno stream NDJSON
import hashlib
import json

def hash_ndjson_records(filepath: str) -> dict[str, str]:
    """Calcola l'hash di ogni record JSON in un file NDJSON per la deduplicazione."""
    seen = {}
    with open(filepath, "r", encoding="utf-8") as f:
        for line_num, line in enumerate(f, 1):
            line = line.strip()
            if not line:
                continue
            try:
                record = json.loads(line)
                # Normalizza prima dell'hashing: ordina le chiavi per output deterministico
                canonical = json.dumps(record, sort_keys=True, separators=(",", ":"))
                digest = hashlib.sha256(canonical.encode("utf-8")).hexdigest()

                if digest in seen:
                    print(f"Riga {line_num}: duplicato della riga {seen[digest]}")
                else:
                    seen[digest] = line_num
            except json.JSONDecodeError:
                print(f"Riga {line_num}: JSON non valido, saltata")

    print(f"Elaborate {line_num} righe, {len(seen)} record unici")
    return seen

hash_ndjson_records("telemetry-events-2026-03.ndjson")
Nota:Passare dal one-shot hashlib.sha256(data) al ciclo a blocchi con .update()quando i file superano i 50–100 MB. Al di sotto di quella soglia, leggere l'intero file con f.read() va bene — l'utilizzo della memoria sarà all'incirca pari alla dimensione del file.

Errori comuni

Passare una str invece di bytes a hashlib.sha256()

Problema: hashlib.sha256('testo') genera TypeError: Unicode-objects must be encoded before hashing. La funzione richiede bytes, non str.

Soluzione: Codificare prima la stringa: hashlib.sha256('testo'.encode('utf-8')). Oppure usare un letterale b'' per valori fissi nel codice.

Before · Python
After · Python
import hashlib
digest = hashlib.sha256("deployment-v4.2.1").hexdigest()
# TypeError: Unicode-objects must be encoded before hashing
import hashlib
digest = hashlib.sha256("deployment-v4.2.1".encode("utf-8")).hexdigest()
# Funziona — restituisce una stringa hex di 64 caratteri
Usare == invece di hmac.compare_digest() per la verifica delle firme

Problema: L'operatore == si interrompe al primo byte diverso. Un attaccante può misurare i tempi di risposta per indovinare la firma corretta byte per byte.

Soluzione: Usare hmac.compare_digest() per tutti i confronti sensibili alla sicurezza — viene eseguito in tempo costante indipendentemente da dove si verifica la discrepanza.

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 ad attacchi temporali
    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)
Codificare in Base64 la stringa esadecimale invece dei byte grezzi

Problema: base64.b64encode(digest.hexdigest().encode()) produce una stringa di 88 caratteri — il doppio dei 44 attesi. Le API che si aspettano SHA-256 codificato in Base64 la rifiuteranno.

Soluzione: Chiamare .digest() (byte grezzi) prima della codifica Base64, non .hexdigest() (stringa esadecimale).

Before · Python
After · Python
import hashlib, base64
hex_str = hashlib.sha256(data).hexdigest()
b64 = base64.b64encode(hex_str.encode())  # 88 caratteri — sbagliato!
import hashlib, base64
raw = hashlib.sha256(data).digest()
b64 = base64.b64encode(raw)  # 44 caratteri — corretto
Caricare un intero file di grandi dimensioni in memoria prima dell'hashing

Problema: hashlib.sha256(open('large.iso', 'rb').read()) carica l'intero file in memoria. Un file da 4 GB richiede 4 GB di RAM solo per il calcolo dell'hash.

Soluzione: Leggere a blocchi con un ciclo e .update(). L'utilizzo della memoria rimane costante indipendentemente dalla dimensione del file.

Before · Python
After · Python
import hashlib
# Carica l'intero file da 4 GB in memoria
digest = hashlib.sha256(open("disk.iso", "rb").read()).hexdigest()
import hashlib
h = hashlib.sha256()
with open("disk.iso", "rb") as f:
    for chunk in iter(lambda: f.read(8192), b""):
        h.update(chunk)
digest = h.hexdigest()  # utilizzo costante della memoria

hashlib vs hmac vs alternative — Confronto rapido

Metodo
Output
Con chiave
Velocità
Streaming file
Richiede installazione
Tipi personalizzati
hashlib.sha256()
hex / bytes
Veloce (C/OpenSSL)
✓ tramite update()
No (stdlib)
encode() manuale
hmac.new()
hex / bytes
Veloce (C/OpenSSL)
✓ tramite update()
No (stdlib)
encode() manuale
hashlib.file_digest()
hex / bytes
Veloce (zero-copy)
✓ (integrato)
No (3.11+)
encode() manuale
cryptography hashes.SHA256()
bytes
Veloce (OpenSSL)
✓ tramite update()
pip install
encode() manuale
subprocess openssl dgst
stringa hex
✗ / ✓
Più lento (fork)
✓ (livello OS)
openssl di sistema
encode() manuale
pyhashcat / custom
variabile
Accelerato GPU
pip install
encode() manuale

Per l'hashing diretto — checksum, chiavi di cache, fingerprinting del contenuto — usare hashlib.sha256(). Passare a hmac.new() non appena si ha bisogno di una chiave segreta (webhook, firme API, autenticazione token). Ricorrere alla libreria cryptography solo se il progetto la usa già per cifratura o TLS — aggiungere una dipendenza da un'estensione C solo per l'hashing è eccessivo quando hashlib è già basato su OpenSSL.

Si può decifrare SHA-256? — Hashing vs cifratura

Risposta breve: no. SHA-256 è una funzione unidirezionale. L'algoritmo è progettato per essere irreversibile — non è possibile ricostruire l'input originale dal digest di 256 bit. Questa non è una limitazione implementativa; è una proprietà matematica della funzione hash. Lo spazio di output di 256 bit è astronomicamente grande (2256 valori possibili), e la funzione scarta informazioni durante i suoi 64 round di compressione.

Gli attaccanti possono tentare attacchi brute-force o dizionario contro input deboli (password comuni, stringhe brevi), ma per qualsiasi input con buona entropia — chiavi API, token casuali, contenuti di file — invertire SHA-256 è computazionalmente impraticabile con l'hardware attuale. Se si ha bisogno di una trasformazione reversibile, usare la cifratura simmetrica:

Python 3.9+ — cifratura vs hashing
# Hashing — unidirezionale, non recuperabile
import hashlib
digest = hashlib.sha256(b"secret-config-value").hexdigest()
# Impossibile risalire a "secret-config-value" dal digest

# Cifratura — bidirezionale, decifrabile con la chiave
from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher = Fernet(key)
encrypted = cipher.encrypt(b"secret-config-value")
decrypted = cipher.decrypt(encrypted)
print(decrypted)  # b"secret-config-value" — originale recuperato

Per generare rapidamente un hash SHA-256 senza installare nulla, lo strumento online funziona interamente nel browser.

Come verificare se una stringa è un hash SHA-256 valido in Python

Un digest SHA-256 esadecimale valido è esattamente 64 caratteri esadecimali (0-9, a-f, A-F). Una rapida validazione prima di elaborare input non affidabili previene errori a valle difficili da interpretare.

Python 3.9+ — validazione del formato SHA-256
import re

def is_sha256_hex(value: str) -> bool:
    """Verifica se una stringa corrisponde al formato digest SHA-256 esadecimale."""
    return bool(re.fullmatch(r"[a-fA-F0-9]{64}", value))

# Casi di test
print(is_sha256_hex("e3b0c44298fc1c149afbf4c8996fb924"
                     "27ae41e4649b934ca495991b7852b855"))  # True — SHA-256 della stringa vuota
print(is_sha256_hex("e3b0c44298fc1c14"))                   # False — troppo corto
print(is_sha256_hex("zzzz" * 16))                          # False — caratteri hex non validi
Nota:Questo valida solo il formato, non se l'hash è stato calcolato da un input specifico. Non è possibile determinare se una stringa esadecimale di 64 caratteri sia un digest SHA-256 "reale" o semplicemente hex casuale — l'output di SHA-256 è indistinguibile da dati casuali per costruzione.

Domande frequenti

Come si calcola l'hash SHA-256 di una stringa in Python?

Si chiama hashlib.sha256() con la stringa codificata in bytes. Le stringhe Python sono Unicode e le funzioni hash operano su byte grezzi, quindi è necessario chiamare .encode("utf-8") prima. Il metodo .hexdigest() restituisce la familiare stringa esadecimale di 64 caratteri.

Python
import hashlib

api_key = "sk_live_9f3a7b2e1d4c"
digest = hashlib.sha256(api_key.encode("utf-8")).hexdigest()
print(digest)
# e3b7c4a1f8d2...64 caratteri esadecimali

È possibile decifrare un hash SHA-256 per ottenere il testo originale?

No. SHA-256 è una funzione unidirezionale — trasforma un input di lunghezza arbitraria in un output fisso di 256 bit, scartando la struttura nel processo. Non esiste inversa matematica. Gli attaccanti possono tentare attacchi brute-force o rainbow table contro input deboli (password corte, parole comuni), ma per qualsiasi input con entropia ragionevole, invertire SHA-256 è computazionalmente impraticabile. Se si ha bisogno di una trasformazione reversibile, usare la cifratura (AES-GCM, Fernet) invece dell'hashing.

Qual è la differenza tra .digest() e .hexdigest()?

.digest() restituisce i 32 byte grezzi dell'hash come oggetto bytes. .hexdigest() restituisce gli stessi dati codificati come stringa esadecimale minuscola di 64 caratteri. Usare .digest() quando si ha bisogno di output binario — input per HMAC, codifica Base64 o scrittura su protocolli binari. Usare .hexdigest() quando si ha bisogno di una stringa leggibile per log, archiviazione in database o confronto di checksum.

Python
import hashlib

h = hashlib.sha256(b"deployment-v4.2.1")
print(len(h.digest()))     # 32 (byte)
print(len(h.hexdigest()))  # 64 (caratteri esadecimali)

Come si calcola il checksum SHA-256 di un file in Python?

Si apre il file in modalità binaria e lo si alimenta all'hasher a blocchi con .update(). Su Python 3.11+, si può usare hashlib.file_digest() per un'API ancora più semplice. Non chiamare mai f.read() su file di grandi dimensioni — carica l'intero file in memoria.

Python
import hashlib

def sha256_file(path: str) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            h.update(chunk)
    return h.hexdigest()

print(sha256_file("release-v4.2.1.tar.gz"))

Come si crea una firma HMAC-SHA256 in Python?

Si usa il modulo hmac con hashlib.sha256 come digestmod. Si passano la chiave segreta e il messaggio come bytes. Il risultato è un hash con chiave che garantisce sia l'integrità che l'autenticità — il ricevitore deve avere la stessa chiave per verificare.

Python
import hmac
import hashlib

secret = b"webhook_secret_9f3a"
payload = b'{"event":"payment.completed","amount":4999}'
signature = hmac.new(secret, payload, hashlib.sha256).hexdigest()
print(signature)  # HMAC esadecimale di 64 caratteri

Come si verifica se una stringa è un digest SHA-256 esadecimale valido?

Un digest SHA-256 esadecimale è esattamente 64 caratteri esadecimali. Si usa una regex o un semplice controllo di lunghezza e caratteri. L'approccio con regex è il più leggibile.

Python
import re

def is_sha256(s: str) -> bool:
    return bool(re.fullmatch(r"[a-fA-F0-9]{64}", s))

print(is_sha256("e3b0c44298fc1c149afbf4c8996fb924"
                 "27ae41e4649b934ca495991b7852b855"))  # True
print(is_sha256("not-a-hash"))  # False

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.