Saltar al contenido
Volver al blog
3 min es

Comprobar si una contraseña está filtrada sin enviársela a nadie

Have I Been Pwned tiene más de 800 millones de contraseñas filtradas. Se puede consultar esa base sin revelar cuál estás buscando, y el truco que lo hace posible se llama k-anonimato. Así lo implementé en Password Sentinel.

  • criptografia
  • have-i-been-pwned
  • k-anonimato
  • extensiones
  • javascript
  • privacidad
Comprobar si una contraseña está filtrada sin enviársela a nadie

Cuando construí Password Sentinel, una extensión de navegador que avisa si una contraseña aparece en filtraciones conocidas, el problema no era técnico sino de confianza. Para saber si tu contraseña está en una brecha hay que compararla contra un corpus de cientos de millones. Y ese corpus no cabe en una extensión.

La solución obvia es preguntarle a un servidor. La solución obvia es también terrible: implicaría que mi extensión envía tus contraseñas a un tercero. Nadie deberia instalar eso, y yo no queria escribirlo.

El truco: preguntar sin decir qué preguntas

Have I Been Pwned resuelve esto con una técnica llamada k-anonimato. La idea es que en vez de preguntar por tu contraseña, preguntas por un grupo lo bastante grande de candidatas como para que la tuya se pierda dentro.

El procedimiento tiene tres pasos:

  • Se calcula el SHA-1 de la contraseña en local, dentro del navegador.
  • Se envían al servidor únicamente los cinco primeros caracteres hexadecimales de ese hash. Nada más.
  • El servidor devuelve todos los hashes de su base que empiezan por ese prefijo, y la comparación final se hace otra vez en local.
Contraseña:  correcthorsebatterystaple
SHA-1:       BE4A8DE9AFCEE30B29C0A0AB29F9E7A9CF4E1B2C
Se envía:    BE4A8
Se recibe:   ~800 sufijos que empiezan por BE4A8
Se compara:  en el navegador, contra la lista recibida

Por qué los números funcionan

Cinco caracteres hexadecimales dan 16^5, algo más de un millón de prefijos posibles. Repartidos sobre un corpus de unos 850 millones de hashes, a cada prefijo le tocan del orden de 800 candidatos.

Eso significa que el servidor sabe que has preguntado por una de unas 800 contraseñas, sin ninguna forma de distinguir cuál. Y no puede afinar con peticiones sucesivas, porque cada consulta devuelve el grupo entero: no hay un segundo intento que estreche el cerco.

Tres detalles que conviene no pasar por alto

SHA-1 aquí no es un fallo. Es lo primero que salta al leer el protocolo, y la respuesta es que SHA-1 no se está usando como función de seguridad: es el índice del corpus. Que SHA-1 tenga colisiones no ayuda a nadie a averiguar qué preguntaste, porque la parte sensible nunca sale del navegador. Otra cosa muy distinta sería usar SHA-1 para almacenar contraseñas, que sí es un error grave.

TLS sigue haciendo falta. El k-anonimato protege frente al servidor. No protege frente a alguien que observe la red y vea el prefijo. Son dos capas distintas y las dos son necesarias.

Cero apariciones no significa contraseña buena. Este es el malentendido más común. Que una contraseña no esté en el corpus solo quiere decir que aún no se ha filtrado. Barcelona2026! probablemente dé cero resultados, y sigue siendo una contraseña pésima: entra en cualquier ataque de diccionario con reglas. La comprobación de filtraciones descarta lo que ya se sabe roto; no certifica lo que queda.

Lo que me llevé de escribirlo

El k-anonimato me interesa menos por la criptografía que por el planteamiento. La pregunta de partida no fue cómo cifro esto, sino qué es lo mínimo que necesita saber la otra parte para responderme. Casi siempre es mucho menos de lo que se acaba enviando por costumbre.

Es el mismo razonamiento que aplico a la analítica de esta web: no guardo direcciones IP porque para contar visitantes no me hacen falta. Guardo un hash con sal y con eso basta. Recoger datos que no necesitas no es neutral: es asumir un riesgo a cambio de nada.

Relacionado: el mismo principio de minimizar lo que se confía en manos ajenas aparece en BinCat, para revocar un token sin derribar toda la sesión.