Imagina el peor lunes: alguien se llevó un volcado de tu tabla de usuarios. Correos, nombres y la columna de contraseñas.
La buena noticia es que no guardas contraseñas en claro, guardas hashes con Argon2id. La mala es que eso importa menos de lo que parece.
Por qué el hash solo no basta
Argon2id hace que cada intento cueste memoria y tiempo. Es una defensa real y hay que usarla. Pero fíjate en qué la sostiene: encarecer cada intento del atacante.
Y el atacante que tiene tu base de datos juega con otras reglas:
- No tiene prisa. No hay rate-limit, no hay bloqueo de cuenta, no hay logs que lo delaten. Puede tardar meses.
- No prueba al azar. Empieza por las listas de contraseñas filtradas, que cubren una fracción incómoda de cualquier base de usuarios reales.
- No usa tu servidor. Usa GPUs alquiladas por horas.
Subir el coste de Argon2 ayuda, pero lo pagas tú en cada login legítimo y él una vez por candidato. La aritmética no está de tu lado: si una contraseña está en las primeras diez mil de una lista filtrada, ningún parámetro razonable la salva.
El problema de fondo es que el atacante tiene todo lo que necesita. El hash, la sal y los parámetros están en el mismo volcado. Puede calcular la función completa por su cuenta.
La idea: que le falte una pieza
¿Y si el cálculo del hash dependiera de un secreto que no está en la base de datos?
La versión simple es el pepper: una clave en la configuración de la aplicación que se mezcla con la contraseña antes de hashear. Ayuda, y tiene dos problemas serios:
- Suele filtrarse con la base de datos. Si el atacante entró al servidor, la variable de entorno estaba a un
catde distancia. - No se puede rotar. Cambiar el pepper invalida todos los hashes a la vez.
Un OPRF resuelve el primero de raíz: el secreto vive en otro servicio, y la parte elegante es que ese servicio nunca ve la contraseña.
Qué es un OPRF, sin matemáticas
Un Oblivious Pseudo-Random Function es un cálculo entre dos partes con esta propiedad rara:
- Tú le mandas tu contraseña cegada — matemáticamente enmascarada. El servidor recibe ruido.
- El servidor aplica su clave secreta
ka ese ruido y te devuelve otro ruido. - Tú lo desciegas y obtienes un valor que solo se puede obtener con esa clave
ky esa contraseña.
El servidor no aprende tu contraseña. Tú no aprendes su clave. Y sin hablar con él, nadie puede calcular ese valor.
Ese valor es lo que hasheas con Argon2id y guardas.
Ahora el atacante con tu base de datos tiene el hash, la sal, los parámetros... y le falta k. No puede probar ni un candidato offline. Para atacar tiene que hablar con el servicio OPRF una vez por intento — y ahí sí hay rate-limit, cuota y logs.
Ha convertido un ataque offline ilimitado en uno online medible. Eso es todo el truco, y es enorme.
La parte que casi nadie implementa: el «verifiable»
Hay una trampa. Si el servidor OPRF está comprometido o simplemente se equivoca de clave, te devuelve valores basura y tú no te enteras: hasheas basura, la guardas, y todos tus usuarios quedan bloqueados. O peor, alguien con control del servidor podría responder con una clave que él conoce.
Por eso la variante que interesa es la verificable (VOPRF, RFC 9497): el servidor adjunta una prueba DLEQ de que usó la clave correcta, y el cliente la verifica contra una clave pública que tiene fijada.
Y aquí está el detalle que vuelve decorativa la mitad de las integraciones que verás por ahí: esa clave pública tiene que estar fijada fuera de banda. Si tu cliente se la pide al mismo servidor al que va a verificar, no has verificado nada — le has pedido al examinado que traiga sus propias respuestas.
Es un error fácil de cometer. Yo lo cometí: dos de mis propios clientes pedían la clave al servidor si el integrador la omitía. Se corrigió haciéndola obligatoria.
Lo que se rompe, dicho antes de que te pase
Ninguna decisión de arquitectura es gratis, y esta tiene tres costes reales:
1. Tus logins dependen de otro servicio. Si el OPRF no responde, no puedes verificar contraseñas. Y aquí la única respuesta correcta es fallar cerrado: si el servicio no está o la prueba no valida, el login falla. La tentación de «degradar a sin endurecer» convierte una caída en un bypass — y un atacante que pueda tumbar el servicio se salta la protección entera.
2. La clave k no rota nunca. Rotarla invalida todos los secretos endurecidos con ella. Es una decisión de una vez y para siempre, y hay que tratarla como tal.
3. Añade latencia al login: una ida y vuelta de red. Medible, pequeña, pero no cero.
Si tu aplicación no puede aceptar el punto 1, esto no es para ti. Prefiero decirlo aquí que después.
Cómo se usa en Django
Escribí las primitivas en Rust siguiendo el RFC 9497 y verificándolas contra los vectores oficiales del Apéndice A.1.2, y las empaqueté para Python. La integración de Django es un hasher que se enchufa donde ya tienes el tuyo:
pip install quipu-oprf-django
# settings.py
PASSWORD_HASHERS = [
"quipu_oprf_django.hashers.OprfArgon2PasswordHasher", # preferido
"django.contrib.auth.hashers.Argon2PasswordHasher", # migración
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
]
QUIPU_OPRF = {
"BASE_URL": os.environ["QUIPU_OPRF_URL"],
"API_KEY": os.environ["QUIPU_OPRF_API_KEY"],
# 64 hex, FIJADA FUERA DE BANDA. No se la pidas al servidor.
"PUBLIC_KEY": os.environ["QUIPU_OPRF_PUBKEY"],
"TIMEOUT": 5.0,
}
La migración es gradual: los usuarios existentes siguen entrando con su hasher viejo y quedan endurecidos la próxima vez que inician sesión.
El paquete es Apache-2.0 a propósito. Vive dentro de tu servidor de autenticación, y meterte ahí una licencia copyleft de red sería impresentable.
Auto-hospedarlo o pagarlo
El servidor es de código abierto (AGPL) y puedes levantarlo tú: es un binario con SQLite y no necesita más. Si haces eso, no me pagas nada y me parece perfecto.
Lo que se cobra en oprf.xiliux.com es no tener que operarlo: disponibilidad, cuota y que la clave k sobreviva a tus despliegues. Porque el día que pierdas esa clave, pierdes todas las contraseñas a la vez.
Lo que esto no es
No es magia y no sustituye nada de lo que ya deberías hacer: sigue usando Argon2id con parámetros serios, sigue teniendo segundo factor, sigue limitando intentos.
Lo que hace es quitarle al atacante el ataque más rentable que tiene contra una base de datos robada. No es poco.
El código está en github.com/isazajuancarlos/quipu. Las primitivas VOPRF son Apache-2.0 y conformes al RFC 9497. El proyecto todavía no tiene auditoría criptográfica independiente, y eso también hay que decirlo.
Xiliux