SHA-256 con Python — hashlib y Ejemplos de Código
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.
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.
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.
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)
# 7d3f8c2a1b9e4f5d6c8a7b3e2f1d9c4a5b8e7f6d3c2a1b9e4f5d6c8a7b3e2f1dEl 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.
import hashlib h = hashlib.sha256() h.update(b"request_id=req_7f3a91bc") h.update(b"×tamp=1741614120") h.update(b"&amount=4999") print(h.hexdigest()) # Equivalente a hashlib.sha256(b"request_id=req_7f3a91bc×tamp=1741614120&amount=4999").hexdigest()
.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.
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 caracteresPara 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.
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.
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.
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).
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:
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áquinassort_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.
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.
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
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")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.
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.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ámetros de hmac.new() para hashing con clave:
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.
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
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
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é
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.
# 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
# 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
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:
pip install 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:
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.
pip install rich
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,
)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.
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
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")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
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.
import hashlib
digest = hashlib.sha256("deployment-v4.2.1").hexdigest()
# TypeError: Unicode-objects must be encoded before hashingimport hashlib
digest = hashlib.sha256("deployment-v4.2.1".encode("utf-8")).hexdigest()
# Correcto — devuelve una cadena hex de 64 caracteresProblema: 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.
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)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).
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
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.
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 constantehashlib vs hmac vs alternativas — Comparación rápida
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:
# 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.
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álidosPreguntas 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.
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.
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.
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.
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.
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")) # FalseHerramientas relacionadas
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.
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.