Hashing vs autenticación: dos objetivos distintos
Un hash criptográfico (p. ej., SHA-256) toma un mensaje y produce una huella fija. Sirve para detectar cambios cuando ya confías en el canal o en la referencia (el “valor esperado”). No usa clave.
La autenticación de mensajes (p. ej., HMAC o un esquema AEAD que autentica) busca integridad + autenticidad: que el mensaje no fue modificado y que proviene de quien conoce una clave. Aquí sí hay secreto compartido (HMAC) o clave de cifrado (AEAD).
- Hash: “¿Este archivo cambió respecto a la huella que ya tengo?”
- HMAC/AEAD: “¿Este mensaje lo generó alguien con la clave y no fue alterado?”
Propiedades clave: colisiones y preimagen (y por qué importan)
Colisiones
Una colisión ocurre si dos entradas distintas producen el mismo hash. En hashes modernos (SHA-256/512) es computacionalmente inviable encontrar colisiones prácticas, pero el concepto es importante porque muchos usos inseguros se basan en asumir “imposible” donde solo es “difícil”.
Riesgo típico: usar hash como “identificador único” en un contexto adversarial (por ejemplo, aceptar un documento si su hash coincide con uno permitido). Si un atacante pudiera fabricar una colisión, podría sustituir contenido sin cambiar la huella.
Preimagen y segunda preimagen
- Resistencia a preimagen: dado un hash
h, es inviable encontrar un mensajemtal quehash(m)=h. - Resistencia a segunda preimagen: dado un mensaje
m, es inviable encontrar otrom2distinto con el mismo hash.
Estas propiedades sostienen usos como fingerprints y verificación de integridad, pero no sustituyen autenticación: un atacante puede modificar el mensaje y recalcular el hash, porque no hay clave que lo impida.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
Usos correctos de hashes (y límites prácticos)
1) Fingerprints (huellas) para verificación manual o fuera de banda
Escenario: publicas un instalador y también publicas su SHA-256 en un canal más confiable (por ejemplo, una página con TLS y un segundo canal como un repositorio verificado). El usuario descarga el archivo y compara.
# Ejemplo conceptual (Linux/macOS): calcular huella SHA-256 de un archivo descargado sha256sum producto_v1.2.3.zip # Comparar con el valor publicado por el proveedorClave: la seguridad depende de que el valor esperado llegue por un canal que el atacante no pueda modificar al mismo tiempo que el archivo.
2) Deduplicación y direccionamiento por contenido (no seguro)
Usar hashes para detectar duplicados o como clave de caché es válido si no hay adversario intentando forzar colisiones o inferir contenido. Ejemplos: almacenamiento interno, cachés, pipelines de build.
- Correcto:
key = SHA-256(bytes)para identificar blobs en un sistema interno. - Precaución: si el sistema es multi-tenant o expuesto, el hash puede filtrar información (p. ej., “¿este archivo existe?”) o permitir ataques de enumeración.
3) Verificación de integridad en canales confiables
Si el canal ya es autenticado (por ejemplo, un enlace interno con autenticación fuerte y controles), un hash puede servir como checksum robusto para detectar corrupción accidental.
Pero si el canal es hostil o el atacante puede modificar tanto el mensaje como el hash, el hash no aporta integridad real.
Cuándo necesitas HMAC o AEAD (integridad con clave)
Necesitas HMAC cuando:
- El mensaje viaja por un canal donde un atacante puede modificarlo.
- Quieres que solo quien conoce la clave pueda producir un tag válido.
- No necesitas confidencialidad, solo autenticidad/integridad (o ya cifras por otro lado).
Necesitas AEAD cuando:
- Quieres confidencialidad + integridad en un solo esquema.
- Quieres autenticar también datos asociados (headers, metadatos) sin cifrarlos.
Regla práctica: hash ≠ MAC. Si hay adversario activo, usa HMAC/AEAD.
Por qué “hash + sal” no reemplaza cifrado (ni autenticación)
“Sal” (salt) es un valor aleatorio que se concatena al dato antes de hashearlo. Se usa principalmente para contraseñas (para evitar tablas arcoíris y hacer únicos los hashes). Fuera de ese contexto, se malinterpreta.
Malentendido 1: “Si le pongo sal, ya está protegido”
Un hash con sal sigue siendo determinista dado (dato, sal). Si el atacante ve salt y hash(salt||dato), puede probar candidatos (ataque de diccionario/fuerza bruta) si el dato tiene baja entropía (IDs, emails, tokens cortos).
Malentendido 2: “Hash con sal es como cifrar”
El cifrado es reversible con clave; el hash no. Para “ocultar” datos, el hash no sirve: solo los destruye. Si necesitas recuperar el dato original, necesitas cifrado. Si necesitas integridad/autenticidad, necesitas MAC/AEAD.
Malentendido 3: “Hash con sal autentica”
Si la sal no es secreta, cualquiera puede recalcular el hash tras modificar el mensaje. Si la “sal” se mantiene secreta, ya no es una sal: es una clave. En ese caso, lo correcto es usar HMAC (diseñado y analizado para eso), no inventar hash(secret||message).
Patrones prácticos para APIs: firmar y validar payloads
Diseño base: firma con HMAC sobre una representación canónica
Objetivo: que el servidor pueda verificar que el cliente (o un intermediario autorizado) generó la solicitud y que no fue alterada.
Componentes típicos a firmar:
- Método HTTP (GET/POST…)
- Ruta (path) y query
- Timestamp
- Nonce (opcional pero recomendado)
- Body (o su hash)
- Identificador de clave (key id) para rotación
Ejemplo de “string to sign” (conceptual):
METHOD
/path
canonical_query
timestamp
nonce
SHA256(body)
Luego:
tag = HMAC-SHA256(secret_key, string_to_sign) signature = base64(tag)El cliente envía timestamp, nonce, key_id y signature en headers. El servidor reconstruye exactamente el mismo string_to_sign y verifica.
Guía paso a paso (implementación mental, independiente de lenguaje)
- Define qué se firma: método, path, query, headers relevantes, body. Evita firmar headers volátiles (p. ej.,
User-Agent). - Normaliza (canoniza) cada parte: ver sección de normalización. Esto evita discrepancias entre cliente y servidor.
- Calcula hash del body (si el body puede ser grande):
body_hash = SHA-256(body_bytes). - Construye el string to sign con separadores inequívocos (por ejemplo
\n) y un orden fijo. - Calcula HMAC con una clave por cliente/servicio:
HMAC-SHA-256. - Envía la firma en un header estándar interno, por ejemplo:
X-Signature, junto conX-Timestamp,X-Nonce,X-Key-Id. - En el servidor: valida formato, reconstruye el string, verifica HMAC en tiempo constante, valida anti-replay (timestamp/nonce), y solo entonces procesa.
Errores comunes al firmar APIs
- Firmar JSON “tal cual”: el mismo objeto puede serializarse con distinto orden de claves o espacios. Resultado: firmas que no verifican.
- Olvidar incluir el método o la ruta: permite reusar una firma válida en otro endpoint (confusión de contexto).
- Usar
hash(secret||message): diseño casero; usa HMAC. - No proteger contra replay: una solicitud capturada puede repetirse.
Protección contra replay: timestamps, nonces y ventanas
Una firma válida puede ser capturada y reenviada. Para mitigarlo, combina:
Timestamps + ventana de tolerancia
- El cliente envía
timestamp(UTC, epoch seconds o ms). - El servidor acepta solo si
abs(now - timestamp) <= window(por ejemplo, 300s).
Esto limita el tiempo de reutilización, pero no evita replays dentro de la ventana.
Nonces (uso único) + almacenamiento
- El cliente genera un
noncealeatorio por solicitud. - El servidor guarda los nonces vistos por
key_iddurante la ventana y rechaza repetidos.
Implementación típica: almacenar (key_id, nonce) en Redis con TTL igual a la ventana. Rechazar si ya existe.
Ventanas y relojes desincronizados
Si hay riesgo de relojes desincronizados, usa una ventana razonable y monitorea rechazos por skew. Alternativa: un endpoint de “time sync” autenticado o usar tiempos del servidor en un desafío previo, pero eso complica el flujo.
Normalización antes de hashear o firmar: evitar discrepancias
La causa más común de fallos de verificación no es la criptografía, sino que cliente y servidor no firman exactamente los mismos bytes. La regla: firma bytes, no estructuras. Para lograrlo, define una representación canónica.
Canonización de query strings
- Ordena parámetros por nombre y luego por valor.
- Usa un esquema de encoding único (por ejemplo, percent-encoding consistente).
- Define cómo tratar parámetros repetidos (
a=1&a=2).
Ejemplo conceptual: canonical_query = sort_and_encode(params).
Canonización de JSON
Opciones:
- JSON canónico: ordenar claves, sin espacios, números normalizados, UTF-8 estricto.
- Firmar el hash del body raw: si controlas que el body se envía como bytes exactos (p. ej., el cliente firma el body ya serializado y el servidor verifica contra el body recibido). Esto evita reserializar, pero exige que proxies no reescriban el body.
Si eliges JSON canónico, documenta reglas exactas (orden de claves, tratamiento de null, floats, Unicode normalizado). Evita floats si puedes: diferencias de representación rompen firmas.
Normalización de Unicode
Dos cadenas visualmente iguales pueden tener bytes distintos (composición vs descomposición). Si firmas texto introducido por usuarios, define una normalización (por ejemplo, NFC) antes de construir el payload a firmar.
Separadores y ambigüedad
Evita concatenar campos sin delimitadores claros. Mejor:
- Usar
\nentre campos y escapar\ndentro de campos, o - Usar un formato estructurado canónico (por ejemplo, un objeto con claves fijas) y luego canonizarlo.
Tabla rápida: qué usar según el problema
| Necesitas | Herramienta | Notas |
|---|---|---|
| Detectar corrupción accidental | Hash (SHA-256) | Canal ya confiable o referencia fuera de banda |
| Comparar/identificar contenido | Hash | No asumir seguridad ante adversarios |
| Integridad + autenticidad sin cifrar | HMAC | Incluye anti-replay si aplica |
| Confidencialidad + integridad | AEAD | Autentica también datos asociados |
| “Proteger” datos de baja entropía | No usar hash simple | Para contraseñas: KDF con sal; para datos recuperables: cifrado |
Checklist operativo para firmar requests en producción
- Define un esquema de firma versionado (p. ej.,
v1) para poder evolucionar. - Incluye contexto en lo firmado: método, ruta, query, timestamp, nonce, body_hash.
- Canoniza query y define reglas para JSON (o firma bytes exactos del body recibido).
- Verifica HMAC en tiempo constante y rechaza si falta cualquier campo.
- Implementa anti-replay con timestamp + nonce y almacenamiento con TTL.
- Soporta rotación de claves con
key_idy múltiples claves activas. - Registra métricas: fallos por skew, nonces repetidos, firmas inválidas (sin loguear secretos ni payloads sensibles).