redzilla
Seguridad

Seguridad práctica: claves, hashes y tokens

“P@ssw0rd!” parece segura y cae en segundos; “caballo correcto batería grapa” parece un chiste y aguanta siglos. La diferencia se llama entropía, y se puede calcular. Bienvenido a la seguridad con números.

≈ 12 min de lectura

En una auditoría a una pyme encontramos la contraseña del NVR pegada en un post-it: "P@ssw0rd!". El encargado de TI estaba tranquilo — tenía mayúscula, número y símbolo, "cumplía la política". La probamos contra un diccionario de claves filtradas y cayó antes del primer sorbo de café: figura en todas las listas del planeta. Mientras tanto, "caballo correcto batería grapa" — que parece chiste — habría aguantado más que la empresa. La diferencia entre ambas no es opinión ni magia: es aritmética, y al final de esta guía la vas a poder hacer de memoria.

Entropía: la fuerza es largo × log₂(alfabeto)

Un atacante no "lee" tu contraseña: la adivina probando combinaciones. La fuerza de una clave es, literalmente, el tamaño del pajar donde está escondida. Ese tamaño se mide en bits de entropía, y la fórmula cabe en una servilleta: entropía = largo × log₂(tamaño del alfabeto). Cada bit adicional duplica las combinaciones que hay que probar.

Piensa en un candado de bicicleta: agregar símbolos raros es hacer cada rueda un poco más grande; agregar caracteres es agregar ruedas completas. Una minúscula aporta log₂(26) = 4,70 bits; un carácter sacado de las cuatro clases (94 símbolos ASCII imprimibles) aporta 6,55. La diferencia por carácter es pequeña — la diferencia por largo es enorme, porque el largo multiplica.

¿Y "P@ssw0rd!"? En teoría, 9 caracteres × 6,55 = 59 bits. Pero la fórmula asume que cada carácter se eligió al azar, y ahí está la trampa: "P@ssw0rd!" es la palabra "password" con las mutaciones a→@ y o→0 que toda herramienta de cracking prueba primero, por regla. Su entropía real es un puñado de bits: está a un par de millones de intentos del inicio de la fila, no a 2⁵⁹. Una passphrase hace lo contrario: elige palabras al azar de la lista EFF de 7776 palabras, y cada palabra vale log₂(7776) = 12,92 bits aunque se lea como cuento infantil.

AlfabetoTamañoBits por unidad
solo minúsculas264,70 / carácter
+ mayúsculas525,70 / carácter
+ dígitos625,95 / carácter
+ símbolos (ASCII imprimible)946,55 / carácter
palabra al azar (lista EFF)777612,92 / palabra
Barras de entropía: 8 minúsculas dan 37,6 bits; 8 caracteres con 4 clases, 52,4; 12 con 4 clases, 78,7; una passphrase de 4 palabras, 51,7 y una de 6 palabras, 77,5 Entropía = largo × log₂(alfabeto) cada bit duplica el trabajo del atacante 50 bits 75 bits 8 minúsculas 37,6 8 chars · 4 clases 52,4 12 chars · 4 clases 78,7 4 palabras EFF 51,7 6 palabras EFF 77,5 0 20 40 60 80 bits a 10¹⁰ intentos/s: 37,6 bits ≈ 10 s · 51,7 ≈ 2 días · 77,5 ≈ 350.000 años
Entropía de políticas típicas. Bajo 50 bits una GPU la rompe en días; sobre 75 bits el ataque muere de viejo.

El ejemplo con números: tu clave contra una GPU

Escenario realista: a un servicio se le filtró la base de datos y las claves estaban con hash rápido (SHA-256 sin sal ni estiramiento — pasa más de lo que crees). Una sola GPU de escritorio prueba del orden de 10¹⁰ hashes por segundo, y en promedio encuentra la clave al recorrer la mitad del espacio. Hagamos la cuenta completa:

  1. La fórmula: entropía = largo × log₂(alfabeto). Para 8 minúsculas: 8 × log₂(26) = 8 × 4,70 = 37,6 bits, es decir 26⁸ ≈ 2,09 × 10¹¹ combinaciones.
  2. Tiempo promedio = (2^bits ÷ 2) ÷ tasa. Con 10¹⁰ intentos/s: 2,09 × 10¹¹ ÷ 2 ÷ 10¹⁰ ≈ 10,4 segundos. Tu clave de 8 minúsculas dura menos que ir a buscar café.
  3. Sube a 12 caracteres con 4 clases (pool 94): 12 × 6,55 = 78,7 bits → 94¹² ≈ 4,76 × 10²³ combinaciones. Promedio: 2,38 × 10¹³ segundos ≈ 754.000 años. La misma GPU, el mismo ataque.
  4. La passphrase: 4 palabras EFF al azar = 4 × 12,92 = 51,7 bits → ≈ 2,1 días contra hash rápido. Justa. Con 6 palabras: 77,5 bits → ≈ 350.000 años, y encima se memoriza caminando al auto.
  5. Moraleja: de 4 a 6 palabras el trabajo del atacante crece 7776² ≈ 60 millones de veces. La entropía no suma — multiplica.

Dos matices honestos. Primero, esto es un ataque offline contra hash rápido: si el servicio guardó bcrypt o scrypt, la tasa se desploma a unos 10⁴ intentos/s y las mismas 8 minúsculas pasan de 10 segundos a unos 4 meses (121 días). Segundo, un ataque online contra un login con límite de intentos es órdenes de magnitud más lento todavía. Puedes jugar con políticas, tasas y escenarios en la calculadora de entropía y tiempo de quiebre.

Passphrase con dados, método clásico: 5 tiradas de un dado común eligen una palabra de la lista EFF (6⁵ = 7776, por eso la lista tiene ese largo). O deja que el generador de claves lo haga con aleatoriedad criptográfica. Lo no negociable: las palabras se eligen al azar, no "las cuatro que se te ocurrieron" — tu cerebro es un pésimo generador de números aleatorios.

El gestor de contraseñas: la conclusión lógica

Sigamos la matemática hasta el final. Necesitas claves únicas por servicio — porque cuando se filtra el foro de aeromodelismo donde te registraste en 2019, lo primero que hacen los atacantes es probar ese mismo correo y clave en tu banco, tu VPN y tu correo corporativo (se llama credential stuffing y es el ataque más rentable que existe). Y necesitas que cada una tenga 75+ bits. Nadie memoriza ochenta cadenas aleatorias de 12 caracteres: la aritmética solo deja una salida.

Esa salida es el gestor de contraseñas: una sola clave maestra tipo passphrase de 6 palabras (77,5 bits, memorizable, no está escrita en ninguna parte) y todo lo demás generado por máquina — 20 caracteres, únicos, que jamás intentas recordar ni teclear. El gestor no es una comodidad: es la única arquitectura donde la fórmula cierra. Y donde el servicio lo permita, segundo factor encima.

Hash: huella digital, no cifrado

Cambio de tema, mismo espíritu numérico. Un hash es una función unidireccional: entra cualquier cosa — una letra, un firmware de 2 GB — y sale una huella de largo fijo. Como la huella dactilar: te identifica sin permitir reconstruirte. Aquí está el error conceptual más repetido del rubro: hash no es cifrado. Lo cifrado se descifra con la clave; el hash no tiene puerta de vuelta — no existe "deshashear". Y tiene efecto avalancha: cambia una letra de la entrada y la huella cambia completa.

Uso 1: verificar integridad. Bajaste un firmware y el fabricante publica su SHA-256: si tu hash calza, el archivo llegó intacto y nadie lo tocó en el camino. Se ve así (y sí, una letra distinta habría dado una huella irreconocible):

huellas de la misma entrada
$ echo -n "redzilla" | md5sum
e9b5a2402e7ab94f63d139974709317a  -
$ echo -n "redzilla" | sha256sum
bcff34d1effbe914707d30410323f9ead560f3f610c8aa6aa710f95993551d15  -

Uso 2: guardar contraseñas. Un servicio bien hecho nunca almacena tu clave: guarda su hash, con dos condiciones. Una sal única por usuario — un valor aleatorio que se mezcla con la clave antes de hashear, para que dos usuarios con la misma clave no compartan hash y las tablas precalculadas no sirvan. Y un algoritmo lento a propósito: bcrypt, scrypt o Argon2 hacen miles de iteraciones justamente para que la GPU del ejemplo baje de 10¹⁰ a 10⁴ intentos por segundo. MD5 y SHA-1 son lo contrario: diseñados en los 90 para ser rápidos, que es exactamente lo que no quieres frente a un atacante.

Encontrar md5(clave) sin sal en un sistema en producción no es "deuda técnica", es una filtración esperando fecha. Una GPU calcula decenas de miles de millones de MD5 por segundo y existen tablas con miles de millones de claves comunes ya hasheadas. Si heredas un sistema así, migra cada hash a bcrypt o scrypt en el siguiente login exitoso de cada usuario — sin esperar el rediseño grande.

Bonus de terreno: los hashes se identifican a ojo por largo y formato. Practica con el calculador de hash y el identificador de hash:

Se ve asíFormatoProbablemente es
e9b5a2402e7ab94f…32 hexMD5
8581bd83aafb4d6d…40 hexSHA-1
bcff34d1effbe914…64 hexSHA-256
$2b$12$N9qo8uLO…empieza con $2b$bcrypt (trae sal y costo adentro)

JWT por dentro: carnet firmado, no sobre cerrado

Los tokens JWT mueven la mitad de las sesiones de internet y cargan el mismo malentendido que el hash. Un JWT son tres bloques en Base64URL unidos por puntos: header (algoritmo), payload (los datos: quién eres, cuándo vence) y firma. La analogía correcta es un carnet plastificado: cualquiera que lo tome puede leer tu nombre y tu RUT — el plástico no esconde nada. Lo que hace el holograma (la firma) es delatar si alguien lo alteró.

Anatomía de un JWT: header y payload son Base64URL legibles por cualquiera; solo la firma requiere la clave para verificarse header.payload.firma — tres bloques Base64URL eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMDI0IiwibmFtZSI6IkFtYW5kYSIs… . X8p2vQ… HEADER { "alg": "HS256", "typ": "JWT" } legible por cualquiera PAYLOAD { "sub": "1024", "name": "Amanda", "iat": 1783684800, "exp": 1783688400 } legible SIN clave: no es cifrado FIRMA HMAC-SHA256( header + "." + payload, secreto ) solo verificable con la clave decodificar ≠ descifrar: la firma garantiza integridad, no secreto iat y exp van en segundos Unix: 1783684800 = 10-jul-2026 12:00 UTC
Un JWT se decodifica sin ninguna clave. La firma solo responde una pregunta: ¿alguien alteró esto?

Compruébalo tú mismo — decodificar el header no requiere ningún secreto, solo deshacer el Base64:

decodificar no es descifrar
$ echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' | base64 -d
{"alg":"HS256","typ":"JWT"}

De ahí salen las dos reglas de oro. Uno: jamás metas secretos en el payload — ni claves, ni datos sensibles, ni nada que no le mostrarías al dueño del token, porque él (y cualquier proxy en el camino) lo lee con dos líneas de terminal. Dos: los claims iat (emitido) y exp (vence) van en segundos Unix, y el exp corto no es pesadez del backend: un JWT robado no se puede "revocar" fácilmente, así que su vida útil es tu único freno. Pega cualquier token en el decodificador JWT — corre 100% en tu navegador — y mira qué está declarando por dentro.

Base64, UUID y otros disfraces

Ya viste al culpable de que el payload sea legible: Base64 es codificación, no cifrado. Transforma bytes arbitrarios en 64 caracteres inofensivos para que sobrevivan viajes por correo, JSON o URLs — y cualquiera lo revierte sin clave, pagando de paso un 33% más de tamaño. Escribir en griego no esconde el mensaje de quien habla griego. Si ves un dato "protegido" en Base64, pásalo por el codificador y saluda al texto plano.

Y para cerrar el kit, los UUID en una frase cada uno. UUID v4: 122 bits de azar criptográfico — imposible de adivinar, perfecto como identificador público. UUID v7: antepone el timestamp en milisegundos, así los IDs nacen ordenados cronológicamente y no destrozan los índices de tu base de datos. Genera ambos (y ULID) en el generador de UUID — pero recuerda que un identificador no es una credencial: lo que protege acceso se llama token, y se firma.

Eso es seguridad práctica en tres hábitos: passphrase larga para lo único que memorizas, gestor para todo lo demás, y reflejo inmediato de desconfianza ante todo lo que "parece" cifrado pero solo está codificado. Pasa tu política actual por la calculadora de entropía y abre uno de tus propios tokens en el decodificador JWT: los números convencen más que cualquier charla.

Herramientas para practicar