SHA-256 con Python — hashlib y Ejemplos de Código

·DevOps Engineer & Python Automation Specialist·Revisado porMaria Santos·Publicado

Usa el Generador de Hash SHA-256 gratuito directamente en tu navegador — sin instalación.

Probar Generador de Hash SHA-256 online →

Cualquier pipeline de despliegue que he construido tarde o temprano necesita verificar el checksum de un archivo, firmar un payload de webhook o generar una huella digital para una clave de caché. El hashing SHA-256 en Python con el módulo integrado hashlib cubre todos esos casos — y ya lo tienes instalado. hashlib.sha256() envuelve la implementación de OpenSSL en CPython, por lo que es rápido y compatible con FIPS sin configuración adicional. Para un hash rápido sin escribir código, el generador SHA-256 en línea te da el resultado al instante. Todos los ejemplos están orientados a Python 3.9+.

  • hashlib.sha256(data).hexdigest() es la forma estándar de hashear bytes — parte de la stdlib, respaldada por OpenSSL.
  • Las cadenas deben codificarse a bytes primero: hashlib.sha256("texto".encode("utf-8")).
  • Para checksums de archivos, alimenta fragmentos mediante .update() — nunca leas un archivo grande entero en memoria.
  • HMAC-SHA256 requiere el módulo hmac: hmac.new(key, msg, hashlib.sha256) — SHA-256 solo no tiene clave.

¿Qué es el hashing SHA-256?

SHA-256 (Secure Hash Algorithm, 256 bits) toma una entrada de longitud arbitraria y produce un digest fijo de 256 bits (32 bytes). La misma entrada siempre produce la misma salida, pero incluso un cambio de un solo bit en la entrada genera un hash completamente distinto — esta propiedad se llama efecto avalancha. SHA-256 forma parte de la familia SHA-2, estandarizada por NIST, y es la base de las huellas digitales de certificados TLS, los IDs de commits de Git, las cabeceras de bloques de Bitcoin y la verificación de integridad de archivos. El algoritmo utiliza una construcción Merkle-Damgård con 64 rondas de compresión para producir su salida de 256 bits.

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

El digest hexadecimal anterior es la representación estándar — 64 caracteres hexadecimales, siempre con la misma longitud independientemente de si hasheas un único byte o una imagen de disco completa.

hashlib.sha256() — El enfoque con la biblioteca estándar

El módulo hashlib viene incluido en toda instalación de Python — no se necesita pip install. Llama a hashlib.sha256() con un argumento de tipo bytes para crear un objeto hash, luego recupera el resultado con .hexdigest() (cadena hexadecimal) o .digest() (bytes sin procesar). El nombre de la función está en minúsculas: sha256, no SHA256.

Python 3.9+ — hash SHA-256 mínimo
import hashlib

# Hashear una cadena de bytes directamente
digest = hashlib.sha256(b"deployment-v4.2.1").hexdigest()
print(digest)
# a8f5f167f44f4964e6c998dee827110c3f1de4d0280c68cba98cf70b4b5157db

El error más común con hashlib.sha256() es pasar un str en lugar de bytes. Las cadenas Python son Unicode y las funciones de hash operan sobre bytes sin procesar. Debes llamar a .encode("utf-8") antes de hashear. Esto le pasa a casi todo el mundo la primera vez.

Python 3.9+ — hashear una cadena
import hashlib

# Las cadenas deben codificarse a bytes antes de hashear
config_key = "redis://cache.internal:6379/0"
digest = hashlib.sha256(config_key.encode("utf-8")).hexdigest()
print(digest)
# 7d3f8c2a1b9e4f5d6c8a7b3e2f1d9c4a5b8e7f6d3c2a1b9e4f5d6c8a7b3e2f1d

El método .update() permite alimentar datos de forma incremental. Llamar h.update(a); h.update(b) es equivalente a hashlib.sha256(a + b). Así es como se hashean archivos en fragmentos sin cargar todo el contenido en memoria.

Python 3.9+ — hashing incremental 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() devuelve 32 bytes sin procesar. .hexdigest() devuelve una cadena hexadecimal de 64 caracteres. Usa .digest() al alimentar el resultado a HMAC, codificación Base64 o protocolos binarios. Usa .hexdigest() para logs, columnas de base de datos y comparación de checksums.

HMAC-SHA256 — Hashing con clave usando el módulo hmac

SHA-256 por sí solo no tiene concepto de clave secreta — cualquiera con la misma entrada puede calcular el mismo hash. Si necesitas demostrar que un mensaje proviene de un remitente específico (verificación de webhooks, firma de solicitudes API, autenticación de tokens), necesitas HMAC. El módulo hmac forma parte de la biblioteca estándar de Python e incorpora la clave al proceso de hashing, de modo que solo quien tiene la clave puede producir o verificar el mismo digest.

Python 3.9+ — HMAC-SHA256 básico
import hmac
import hashlib

# Verificación de firma de 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 hexadecimal de 64 caracteres

Para verificar un HMAC entrante se debe usar hmac.compare_digest() en lugar del operador ==. El operador de igualdad es vulnerable a ataques de temporización — se detiene en el primer byte que no coincide, y un atacante puede medir los tiempos de respuesta para adivinar la firma correcta byte a byte. compare_digest() se ejecuta en tiempo constante independientemente de dónde ocurra la discrepancia.

Python 3.9+ — verificar una firma de webhook
import hmac
import hashlib

def verify_webhook(payload: bytes, received_sig: str, secret: bytes) -> bool:
    """Verifica una firma de webhook usando comparación en tiempo constante."""
    expected = hmac.new(secret, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_sig)

# Simulando una verificación de webhook estilo Stripe
incoming_payload = b'{"event":"payment.completed","amount":4999}'
incoming_signature = "a1b2c3d4e5f6..."  # del encabezado X-Signature
webhook_secret = b"whsec_9f3a7b2e1d4c"

if verify_webhook(incoming_payload, incoming_signature, webhook_secret):
    print("Firma válida — procesa el evento")
else:
    print("Firma no coincide — rechaza la solicitud")

Firma de solicitudes con HMAC-SHA256

La firma de solicitudes API sigue el mismo principio: construye una cadena canónica a partir de los componentes de la solicitud (método, ruta, timestamp, hash del cuerpo) y fírmala con tu clave secreta. AWS Signature V4, Stripe y los webhooks de GitHub usan variaciones de este patrón.

Python 3.9+ — firmar una solicitud 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 para una solicitud API."""
    timestamp = str(int(time.time()))
    body_hash = hashlib.sha256(body).hexdigest()

    # Cadena canónica: método + ruta + timestamp + hash del cuerpo
    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}"

# Uso
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 codificado en Base64

Algunas APIs (AWS Signature V4, diversas pasarelas de pago) esperan el resultado HMAC como cadena codificada en Base64 en lugar de hex. La diferencia: hex usa 64 caracteres, Base64 usa 44 caracteres para el mismo digest de 32 bytes.

Python 3.9+ — HMAC-SHA256 codificado en Base64
import hmac
import hashlib
import base64

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

# Salida hex: 64 caracteres
hex_sig = hmac.new(secret, message, hashlib.sha256).hexdigest()
print(f"Hex:    {hex_sig}")

# Salida Base64: 44 caracteres (más corta, común en cabeceras HTTP)
raw_sig = hmac.new(secret, message, hashlib.sha256).digest()
b64_sig = base64.b64encode(raw_sig).decode("ascii")
print(f"Base64: {b64_sig}")

Hashear datetime, UUID y objetos personalizados

SHA-256 opera sobre bytes sin procesar, por lo que los tipos que no son bytes — datetime, UUID, dataclasses, modelos Pydantic — deben serializarse a bytes antes de hashearlos. No hay conversión automática; tú eliges la representación canónica. Para un hashing determinista entre sistemas, usa siempre una codificación explícita y un formato de serialización estable (ISO 8601 para datetimes, la forma estándar de cadena para UUIDs, JSON con claves ordenadas para dicts).

Python 3.9+ — hashear un datetime y un UUID
import hashlib
import uuid
from datetime import datetime, timezone

# datetime — usa ISO 8601 con offset UTC explícito para portabilidad
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 — hashea la forma canónica de cadena (minúsculas, con guiones)
record_id = uuid.uuid4()
uuid_hash = hashlib.sha256(str(record_id).encode("utf-8")).hexdigest()
print(f"hash UUID: {uuid_hash[:16]}...")

Para objetos personalizados, serializa a una representación canónica de bytes antes de hashear. JSON con claves ordenadas funciona bien para objetos tipo dict:

Python 3.9+ — hashear un objeto personalizado
import hashlib
import json
from dataclasses import dataclass, asdict

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

def hash_event(event: Event) -> str:
    """Hashea una instancia de dataclass usando JSON con claves ordenadas para determinismo."""
    canonical = json.dumps(asdict(event), sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()

e = Event(id="evt_4f2a", type="payment.completed", amount=4999, timestamp="2026-03-28T12:00:00Z")
print(hash_event(e))  # estable entre ejecuciones y máquinas
Nota:Ordena siempre las claves del dict (sort_keys=True) al hashear objetos serializados como JSON. El orden de inserción de dicts se preserva en Python 3.7+ pero puede diferir entre rutas de serialización, produciendo hashes distintos para datos idénticos.

Checksum SHA-256 de archivos — Verifica descargas y artefactos

Calcular el checksum SHA-256 de un archivo es uno de los usos más comunes del algoritmo. Lo ves en todas partes: páginas de releases de binarios Go, archivos wheel de Python, manifiestos de imágenes Docker, actualizaciones de firmware. La clave es leer el archivo en fragmentos en lugar de cargarlo todo de una vez — una imagen ISO de 2 GB no debería requerir 2 GB de RAM solo para hashearla.

Python 3.9+ — checksum SHA-256 de un archivo (por fragmentos)
import hashlib

def sha256_checksum(filepath: str, chunk_size: int = 8192) -> str:
    """Calcula el hash SHA-256 de un archivo leyendo en fragmentos para ahorrar 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()

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

Python 3.11 añadió hashlib.file_digest(), que realiza la lectura por fragmentos internamente y puede usar optimizaciones de copia cero en plataformas compatibles. Si usas 3.11 o posterior, prefiere esta función sobre el bucle manual.

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())

Verificar un archivo descargado contra un checksum conocido

Python 3.9+ — verificación de checksum
import hashlib
import hmac as hmac_mod  # solo para compare_digest

def verify_checksum(filepath: str, expected_hex: str) -> bool:
    """Verifica el checksum SHA-256 usando comparación en tiempo constante."""
    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())

# Verificar un artefacto de release
expected = "a8f5f167f44f4964e6c998dee827110c3f1de4d0280c68cba98cf70b4b5157db"
if verify_checksum("/tmp/release-v4.2.1.tar.gz", expected):
    print("El checksum coincide — el archivo está íntegro")
else:
    print("El checksum no coincide — el archivo puede estar corrupto o manipulado")
Nota:Usa siempre hmac.compare_digest() para comparar checksums, incluso cuando no haya clave secreta. La comparación en tiempo constante evita filtraciones de información basadas en temporización. El operador == funciona correctamente pero no es seguro en contextos sensibles a la seguridad.

SHA-256 con codificación Base64

Algunos protocolos esperan el digest SHA-256 como cadena Base64 en lugar de hex. Cabeceras HTTP como Content-Digest e Integrity (Subresource Integrity en navegadores) usan Base64, y las firmas JWT están codificadas en Base64url. El truco es codificar en Base64 los bytes sin procesar de .digest(), no la cadena hexadecimal.

Python 3.9+ — SHA-256 codificado en Base64
import hashlib
import base64

data = b"integrity check payload"

# Correcto: Base64 de bytes sin procesar (32 bytes → 44 caracteres Base64)
raw_digest = hashlib.sha256(data).digest()
b64_digest = base64.b64encode(raw_digest).decode("ascii")
print(f"sha256-{b64_digest}")
# sha256-<44 caracteres>

# Incorrecto: Base64 de la cadena hex (64 bytes ASCII → 88 caracteres Base64 — el doble del tamaño)
hex_digest = hashlib.sha256(data).hexdigest()
wrong = base64.b64encode(hex_digest.encode()).decode()
print(f"Longitud incorrecta: {len(wrong)} chars")  # 88 — no es lo que esperan las APIs
Advertencia:Codificar en Base64 la cadena hexadecimal en lugar de los bytes sin procesar es un error común que produce una salida el doble de larga de lo esperado. Las APIs lo rechazarán, y el mensaje de error normalmente no da ninguna pista sobre el motivo. Llama siempre a .digest(), no a .hexdigest(), antes de codificar en Base64.

Referencia de hashlib.sha256()

El constructor y los métodos de un objeto hash SHA-256:

Parámetro / Método
Tipo
Descripción
data (posicional)
bytes
Datos iniciales a hashear — equivalente a llamar update(data) justo después de la construcción
.update(data)
bytes
Alimenta bytes adicionales al estado del hash — puede llamarse varias veces para entradas fragmentadas
.digest()
→ bytes
Devuelve los 32 bytes binarios sin procesar — úsalo para entradas HMAC, protocolos binarios y codificación Base64
.hexdigest()
→ str
Devuelve la cadena hexadecimal de 64 caracteres en minúsculas — representación estándar para checksums y verificación
.copy()
→ hash object
Devuelve un clon del estado actual del hash — permite bifurcar un digest sin recomputar desde el inicio
hashlib.sha256()
constructor
Crea un nuevo objeto hash SHA-256 respaldado por OpenSSL en CPython — usedfips=True en 3.9+ restringe a algoritmos aprobados por FIPS

Parámetros de hmac.new() para hashing con clave:

Parámetro
Tipo
Descripción
key
bytes
La clave secreta — debe ser bytes, no str
msg
bytes | None
Mensaje inicial a autenticar — None por defecto, puedes llamar a update() después
digestmod
str | callable
Algoritmo de hash: pasa hashlib.sha256 o la cadena "sha256"

La librería cryptography — Una API alternativa para SHA-256

El paquete cryptography ofrece una API diferente para SHA-256 a través de sus primitivas hazmat. Raramente la utilizo cuando solo necesito un hash — hashlib es más simple y no tiene dependencias externas. Pero si tu proyecto ya depende de cryptography para TLS, X.509 o cifrado simétrico, usar su API de hash mantiene todo bajo una misma librería y ofrece un manejo de errores consistente.

Python 3.9+ — SHA-256 con la librería 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 bytes sin procesar

print(result.hex())  # cadena hex de 64 caracteres
# a8f5f167f44f4964e6c998dee827110c3f1de4d0280c68cba98cf70b4b5157db
Advertencia:La librería cryptography requiere pip install cryptography. El objeto hash es de un solo uso: llamar a .finalize() una segunda vez lanza AlreadyFinalized. Usa .copy() antes de finalizar si necesitas bifurcar el estado del hash.

Hashear datos de un archivo y de una respuesta API

Dos escenarios aparecen constantemente: hashear un archivo en disco para verificar un artefacto de release, y hashear el cuerpo de una respuesta HTTP para usarlo como clave de caché o verificar un webhook.

Leer archivo → Calcular SHA-256 → Comparar

Python 3.9+ — hashear un backup de configuración con manejo de errores
import hashlib
import sys

def hash_file_safe(filepath: str) -> str | None:
    """Hashea un archivo con manejo de errores adecuado."""
    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"Error: {filepath} no encontrado", file=sys.stderr)
        return None
    except PermissionError:
        print(f"Error: sin permiso de lectura para {filepath}", file=sys.stderr)
        return None

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

Respuesta HTTP → Hashear cuerpo como clave de caché

Python 3.9+ — hashear una respuesta API
import hashlib
import urllib.request
import json

def fetch_and_hash(url: str) -> tuple[dict, str]:
    """Obtiene JSON de una API y devuelve tanto los datos como su 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"Error al obtener {url}: {exc}") from exc

# Clave de caché basada en el contenido de la respuesta
data, digest = fetch_and_hash("https://api.exchange.internal/v2/rates")
print(f"Hash de respuesta: {digest[:16]}...")
print(f"EUR/USD: {data.get('rates', {}).get('EUR', 'N/A')}")

Para una verificación rápida sin instalación, el generador SHA-256 de ToolDeck funciona completamente en tu navegador — sin código necesario.

Hashing SHA-256 desde la línea de comandos

A veces solo necesitas un hash rápido en la terminal durante un incidente o un despliegue. El módulo hashlib de Python no tiene un subcomando CLI integrado (a diferencia de python3 -m json.tool), pero puedes usar un one-liner o las herramientas del sistema.

bash — hashear una cadena desde la línea de comandos
# One-liner de 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 (multiplataforma)
echo -n "deployment-v4.2.1" | openssl dgst -sha256
bash — hashear un archivo
# Hashear un tarball de release
sha256sum release-v4.2.1.tar.gz
# o bien
openssl dgst -sha256 release-v4.2.1.tar.gz

# Verificar contra un checksum conocido
echo "a8f5f167f44f4964e6c998dee827110c release-v4.2.1.tar.gz" | sha256sum -c -
# release-v4.2.1.tar.gz: OK
Nota:Usa siempre echo -n (sin salto de línea al final) al hashear cadenas en la línea de comandos. Un echo sin opciones añade \n, lo que cambia el hash. Este es el motivo número uno por el que la gente obtiene hashes distintos entre Python y la shell.

Alternativa de alto rendimiento — hashlib con OpenSSL y pycryptodome

En CPython, hashlib.sha256() ya delega a la implementación C de OpenSSL, por lo que es rápido — típicamente más de 500 MB/s en hardware moderno.

Si el hashing SHA-256 aparece en tu perfilador — por ejemplo, calculas checksums para miles de archivos en un pipeline CI o hasheas cada cuerpo de solicitud en una API de alto volumen — existen dos opciones: optimizar el patrón de llamada a hashlib, o cambiar a pycryptodome para una API criptográfica unificada que también cubre SHA-3 y BLAKE2:

bash — instalar 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

Para el hashing paralelo de archivos de alto volumen, las mayores ganancias provienen de reducir la sobrecarga de Python mediante tamaños de fragmento más grandes y el uso de threads:

Python 3.9+ — hashing de archivos en lote con hashlib
import hashlib
import os
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

def hash_file(path: Path) -> tuple[str, str]:
    """Hashea un único archivo y devuelve (ruta, digest hex)."""
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):  # fragmentos de 64 KB
            h.update(chunk)
    return str(path), h.hexdigest()

def hash_directory(directory: str, pattern: str = "*.tar.gz") -> dict[str, str]:
    """Hashea en paralelo todos los archivos coincidentes usando threads."""
    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

# Hashear en paralelo todos los artefactos de release
checksums = hash_directory("/var/releases", "*.tar.gz")
for path, digest in checksums.items():
    print(f"{digest}  {path}")

Usar fragmentos de 64 KB en lugar de 8 KB reduce el número de llamadas Python-a-C en 8x. Los threads funcionan bien aquí porque el GIL se libera durante el hashing a nivel C — el cuello de botella es el I/O de disco, no la CPU.

Salida en terminal con resaltado de sintaxis

La librería rich es útil cuando necesitas verificar un lote de archivos y quieres una tabla que muestre el estado correcto/incorrecto por archivo en lugar de ver hashes hexadecimales desfilar por pantalla.

bash — instalar rich
pip install rich
Python 3.9+ — salida rich para verificación de hashes
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:
    """Hashea archivos y muestra los resultados con verificación con código de colores."""
    table = Table(title="Verificación SHA-256")
    table.add_column("Archivo", style="cyan")
    table.add_column("SHA-256", style="dim", max_width=20)
    table.add_column("Estado")

    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]✗ NO COINCIDE[/red]"
        table.add_row(name, f"{digest[:16]}...", status)

    console.print(table)

# Uso
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:La salida de rich es solo para visualización en terminal. No escribas códigos de escape ANSI en archivos de log ni en respuestas API — elimínalos con console.print(data, highlight=False) o redirige a un archivo con Console(file=open(...)).

Trabajar con archivos grandes

El patrón de .update() por fragmentos maneja archivos de cualquier tamaño con un uso constante de memoria. Para archivos muy grandes (imágenes de disco de varios GB, copias de seguridad de base de datos), la preocupación principal pasa de la memoria al feedback del usuario — hashear 10 GB a 500 MB/s tarda 20 segundos, y el silencio durante ese tiempo pone nervioso a cualquiera.

Python 3.9+ — hashear archivos grandes con reporte de progreso
import hashlib
import os

def sha256_with_progress(filepath: str) -> str:
    """Hashea un archivo grande con reporte de progreso en 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):  # fragmentos de 1 MB
            h.update(chunk)
            bytes_read += len(chunk)
            pct = (bytes_read / file_size) * 100
            print(f"\r  Hasheando: {pct:.1f}% ({bytes_read >> 20} MB / {file_size >> 20} MB)",
                  end="", flush=True)

    print()  # nueva línea al terminar
    return h.hexdigest()

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

NDJSON / JSON Lines — Hashear cada registro por separado

Python 3.9+ — hashear registros individuales en un stream NDJSON
import hashlib
import json

def hash_ndjson_records(filepath: str) -> dict[str, str]:
    """Hashea cada registro JSON de un archivo NDJSON para deduplicación."""
    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)
                # Normalizar antes de hashear: ordenar claves para salida determinista
                canonical = json.dumps(record, sort_keys=True, separators=(",", ":"))
                digest = hashlib.sha256(canonical.encode("utf-8")).hexdigest()

                if digest in seen:
                    print(f"Línea {line_num}: duplicado de la línea {seen[digest]}")
                else:
                    seen[digest] = line_num
            except json.JSONDecodeError:
                print(f"Línea {line_num}: JSON inválido, omitida")

    print(f"Procesadas {line_num} líneas, {len(seen)} registros únicos")
    return seen

hash_ndjson_records("telemetry-events-2026-03.ndjson")
Nota:Cambia del one-shot hashlib.sha256(data) al bucle con .update() por fragmentos cuando los archivos superen 50-100 MB. Por debajo de ese umbral, leer el archivo completo con f.read() está bien — el uso de memoria será aproximadamente igual al tamaño del archivo.

Errores comunes

Pasar un str en lugar de bytes a hashlib.sha256()

Problema: hashlib.sha256('texto') lanza TypeError: Unicode-objects must be encoded before hashing. La función requiere bytes, no str.

Solución: Codifica la cadena primero: hashlib.sha256('texto'.encode('utf-8')). O usa un literal b'' para valores fijos.

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()
# Correcto — devuelve una cadena hex de 64 caracteres
Usar == en lugar de hmac.compare_digest() para verificar firmas

Problema: El operador == se detiene en el primer byte que no coincide. Un atacante puede medir el tiempo de respuesta para adivinar la firma correcta byte a byte.

Solución: Usa hmac.compare_digest() para todas las comparaciones sensibles a la seguridad — se ejecuta en tiempo constante independientemente de dónde ocurra la discrepancia.

Before · Python
After · Python
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()
if received_sig == expected_sig:  # vulnerable a ataques de temporización
    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):  # tiempo constante
    process_webhook(body)
Codificar en Base64 la cadena hexadecimal en lugar de los bytes sin procesar

Problema: base64.b64encode(digest.hexdigest().encode()) produce una cadena de 88 caracteres — el doble de los 44 esperados. Las APIs que esperan SHA-256 en Base64 lo rechazarán.

Solución: Llama a .digest() (bytes sin procesar) antes de codificar en Base64, no a .hexdigest() (cadena hexadecimal).

Before · Python
After · Python
import hashlib, base64
hex_str = hashlib.sha256(data).hexdigest()
b64 = base64.b64encode(hex_str.encode())  # 88 chars — ¡incorrecto!
import hashlib, base64
raw = hashlib.sha256(data).digest()
b64 = base64.b64encode(raw)  # 44 chars — correcto
Cargar un archivo grande entero en memoria antes de hashear

Problema: hashlib.sha256(open('large.iso', 'rb').read()) carga todo el archivo en memoria. Un archivo de 4 GB requiere 4 GB de RAM solo para el cálculo del hash.

Solución: Lee en fragmentos con un bucle y .update(). El uso de memoria se mantiene constante independientemente del tamaño del archivo.

Before · Python
After · Python
import hashlib
# Carga todo el archivo de 4 GB en 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()  # uso de memoria constante

hashlib vs hmac vs alternativas — Comparación rápida

Método
Salida
Con clave
Velocidad
Streaming de archivos
Requiere instalación
Tipos personalizados
hashlib.sha256()
hex / bytes
Rápido (C/OpenSSL)
✓ mediante update()
No (stdlib)
encode() manual
hmac.new()
hex / bytes
Rápido (C/OpenSSL)
✓ mediante update()
No (stdlib)
encode() manual
hashlib.file_digest()
hex / bytes
Rápido (zero-copy)
✓ (integrado)
No (3.11+)
encode() manual
cryptography hashes.SHA256()
bytes
Rápido (OpenSSL)
✓ mediante update()
pip install
encode() manual
subprocess openssl dgst
hex string
✗ / ✓
Más lento (fork)
✓ (nivel OS)
System openssl
encode() manual
pyhashcat / custom
varía
Acelerado por GPU
pip install
encode() manual

Para el hashing directo — checksums, claves de caché, huellas de contenido — quédate con hashlib.sha256(). Cambia a hmac.new() en el momento en que necesites una clave secreta (webhooks, firmas API, autenticación de tokens). Recurre a la librería cryptography solo si tu proyecto ya la usa para cifrado o TLS — añadir una dependencia de extensión C solo para hashing es excesivo cuando hashlib ya está respaldado por OpenSSL.

¿Se puede descifrar SHA-256? — Hashing vs cifrado

Respuesta corta: no. SHA-256 es una función unidireccional. El algoritmo está diseñado para ser irreversible — no puedes reconstruir la entrada original a partir del digest de 256 bits. Esta no es una limitación de implementación; es una propiedad matemática de la función hash. El espacio de salida de 256 bits es astronómicamente grande (2256 valores posibles), y la función descarta información durante sus 64 rondas de compresión.

Los atacantes pueden intentar ataques de fuerza bruta o de diccionario contra entradas débiles (contraseñas comunes, cadenas cortas), pero para cualquier entrada con una entropía razonable — claves API, tokens aleatorios, contenido de archivos — revertir SHA-256 es computacionalmente inviable con el hardware actual. Si necesitas una transformación reversible, usa cifrado simétrico:

Python 3.9+ — cifrado vs hashing
# Hashing — unidireccional, no se puede recuperar el original
import hashlib
digest = hashlib.sha256(b"secret-config-value").hexdigest()
# No hay forma de obtener "secret-config-value" a partir del digest

# Cifrado — bidireccional, se puede descifrar con la clave
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" — original recuperado

Para generar un hash SHA-256 de forma rápida sin instalar nada, la herramienta en línea funciona completamente en tu navegador.

Cómo verificar si una cadena es un hash SHA-256 válido en Python

Un digest hexadecimal SHA-256 válido tiene exactamente 64 caracteres hexadecimales (0-9, a-f, A-F). Una validación rápida antes de procesar entradas no confiables evita errores confusos más adelante.

Python 3.9+ — validar formato SHA-256
import re

def is_sha256_hex(value: str) -> bool:
    """Comprueba si una cadena coincide con el formato de digest hexadecimal SHA-256."""
    return bool(re.fullmatch(r"[a-fA-F0-9]{64}", value))

# Casos de prueba
print(is_sha256_hex("e3b0c44298fc1c149afbf4c8996fb924"
                     "27ae41e4649b934ca495991b7852b855"))  # True — SHA-256 de cadena vacía
print(is_sha256_hex("e3b0c44298fc1c14"))                   # False — demasiado corto
print(is_sha256_hex("zzzz" * 16))                          # False — caracteres hex inválidos
Nota:Esto valida solo el formato, no si el hash fue calculado a partir de una entrada en particular. No hay forma de determinar si una cadena hexadecimal de 64 caracteres es un digest SHA-256 "real" o simplemente hex aleatorio — la salida de SHA-256 es indistinguible de datos aleatorios por diseño.

Preguntas frecuentes

¿Cómo hasheo una cadena con SHA-256 en Python?

Llama a hashlib.sha256() con la cadena codificada a bytes. Las cadenas en Python son Unicode y las funciones de hash operan sobre bytes sin procesar, así que debes llamar a .encode("utf-8") primero. El método .hexdigest() devuelve la conocida cadena hexadecimal de 64 caracteres.

Python
import hashlib

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

¿Se puede descifrar un hash SHA-256 para obtener el texto original?

No. SHA-256 es una función unidireccional — mapea una entrada de longitud arbitraria a una salida fija de 256 bits y descarta la estructura en el proceso. No existe inversa matemática. Los atacantes pueden intentar fuerza bruta o búsquedas en tablas rainbow contra entradas débiles (contraseñas cortas, palabras comunes), pero para cualquier entrada con entropía razonable, revertir SHA-256 es computacionalmente inviable. Si necesitas una transformación reversible, usa cifrado (AES-GCM, Fernet) en lugar de hashing.

¿Cuál es la diferencia entre .digest() y .hexdigest()?

.digest() devuelve los 32 bytes sin procesar del hash como objeto bytes. .hexdigest() devuelve los mismos datos codificados como cadena hexadecimal de 64 caracteres en minúsculas. Usa .digest() cuando necesites salida binaria — para alimentar HMAC, codificación Base64 o protocolos binarios. Usa .hexdigest() cuando necesites una cadena legible para logs, almacenamiento en base de datos o comparación de checksums.

Python
import hashlib

h = hashlib.sha256(b"deployment-v4.2.1")
print(len(h.digest()))     # 32 (bytes)
print(len(h.hexdigest()))  # 64 (caracteres hexadecimales)

¿Cómo calculo el checksum SHA-256 de un archivo en Python?

Abre el archivo en modo binario y aliméntalo al hasher en fragmentos con .update(). En Python 3.11+, usa hashlib.file_digest() para una API aún más simple. Nunca llames a f.read() en archivos grandes — eso carga todo el archivo en 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"))

¿Cómo creo una firma HMAC-SHA256 en Python?

Usa el módulo hmac con hashlib.sha256 como digestmod. Pasa la clave secreta y el mensaje como bytes. El resultado es un hash con clave que demuestra tanto integridad como autenticidad — el receptor necesita la misma clave para verificarlo.

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 hexadecimal de 64 caracteres

¿Cómo valido si una cadena es un digest hexadecimal SHA-256 válido?

Un digest hexadecimal SHA-256 tiene exactamente 64 caracteres hexadecimales. Usa una expresión regular o una verificación simple de longitud más caracteres. El enfoque con regex es el más legible.

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("no-es-un-hash"))  # False

Herramientas relacionadas

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 SantosRevisor técnico

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.