Comprovar si una contrasenya està filtrada sense enviar-la a ningú
Have I Been Pwned té més de 800 milions de contrasenyes filtrades. Es pot consultar sense revelar quina cerques: el truc es diu k-anonimat. Així el vaig implementar a Password Sentinel.
- criptografia
- have-i-been-pwned
- k-anonimato
- extensiones
- javascript
- privacidad
Quan vaig construir Password Sentinel, una extensió de navegador que avisa si una contrasenya apareix en filtracions conegudes, el problema no era tècnic sinó de confiança. Per saber si la teva contrasenya és en una bretxa cal comparar-la contra un corpus de centenars de milions. I aquest corpus no cap en una extensió.
La solució òbvia és preguntar-ho a un servidor. La solució òbvia és també terrible: implicaria que la meva extensió envia les teves contrasenyes a un tercer. Ningú no hauria d'instal·lar això, i jo no ho volia escriure.
El truc: preguntar sense dir quines preguntes
Have I Been Pwned resol això amb una tècnica anomenada k-anonimat. La idea és que en lloc de preguntar per la teva contrasenya, preguntes per un grup prou gran de candidates perquè la teva s'hi perdi dins.
El procediment té tres passos:
- Es calcula el
SHA-1de la contrasenya en local, dins del navegador. - S'envien al servidor únicament els cinc primers caràcters hexadecimals d'aquest hash. Res més.
- El servidor retorna tots els hashes de la seva base que comencen per aquest prefix, i la comparació final es fa una altra vegada en local.
Contrasenya: correcthorsebatterystaple
SHA-1: BE4A8DE9AFCEE30B29C0A0AB29F9E7A9CF4E1B2C
S'envia: BE4A8
Es rep: ~800 sufixos que comencen per BE4A8
Es compara: al navegador, contra la llista rebuda
Per què els números funcionen
Cinc caràcters hexadecimals donen 16^5, una mica més d'un milió de prefixos possibles. Repartits sobre un corpus d'uns 850 milions de hashes, a cada prefix li toquen de l'ordre de 800 candidats.
Això vol dir que el servidor sap que has preguntat per una d'unes 800 contrasenyes, sense cap manera de distingir quina. I no pot afinar amb peticions successives, perquè cada consulta retorna el grup sencer: no hi ha un segon intent que estrenyi el setge.
Tres detalls que convé no passar per alt
SHA-1 aquí no és una fallada. És el primer que salta en llegir el protocol, i la resposta és que SHA-1 no s'està fent servir com a funció de seguretat: és l'índex del corpus. Que SHA-1 tingui col·lisions no ajuda ningú a esbrinar què vas preguntar, perquè la part sensible mai surt del navegador. Una altra cosa ben diferent seria fer servir SHA-1 per emmagatzemar contrasenyes, que sí és un error greu.
TLS continua fent falta. El k-anonimat protegeix davant del servidor. No protegeix davant d'algú que observi la xarxa i vegi el prefix. Són dues capes diferents i les dues són necessàries.
Zero aparicions no vol dir contrasenya bona. Aquest és el malentès més comú. Que una contrasenya no sigui al corpus només vol dir que encara no s'ha filtrat. Barcelona2026! probablement doni zero resultats, i continua sent una contrasenya pèssima: entra en qualsevol atac de diccionari amb regles. La comprovació de filtracions descarta el que ja se sap trencat; no certifica el que queda.
El que em vaig endur d'escriure-ho
El k-anonimat m'interessa menys per la criptografia que pel plantejament. La pregunta de partida no va ser com xifro això, sinó què és el mínim que necessita saber l'altra part per respondre'm. Quasi sempre és molt menys del que s'acaba enviant per costum.
És el mateix raonament que aplico a l'analítica d'aquest web: no deso adreces IP perquè per comptar visitants no me les fan falta. Deso un hash amb sal i amb això n'hi ha prou. Recollir dades que no necessites no és neutral: és assumir un risc a canvi de res.
Relacionat: el mateix principi de minimitzar el que es confia a mans alienes apareix a BinCat, per revocar un token sense enderrocar tota la sessió.