Deze blog telt twaalf artikelen over hoe het hoort: dat lengte zwaarder weegt dan symbolen, dat een manager geen gemak is maar de enige manier om de regel te halen, dat een percentage niets zegt.
Dat opschrijven is makkelijk. Ongemakkelijk wordt het als je je eigen site langs dezelfde lat legt. Dat hebben we gedaan, en er waren drie dingen mis. Ze staan hier voluit, met de cijfers die ze verraadden, want een beveiligingssite die alleen zijn successen publiceert is een stuk minder waard.
De checker bevroor terwijl je typte
De checker analyseert je wachtwoord met zxcvbn, een bibliotheek die patronen zoekt in plaats van hoofdletters te tellen. Werkt uitstekend, en heeft een prijs: hij draait in één keer, op dezelfde thread die de pagina tekent. Zolang hij draait kan de browser niets anders doen.
Die prijs loopt snel op met de lengte. Gemeten met precies het pakket dat de site
serveert, en met wachtwoorden zoals mensen ze echt typen — woorden, jaartallen,
@ in plaats van a:
| lengte | één keer draaien |
|---|---|
| 16 tekens | 45 ms |
| 20 tekens | 121 ms |
| 32 tekens | 273 ms |
| 64 tekens | 843 ms |
| 128 tekens | 3.621 ms |
Tot zover normaal. Het probleem was hoe vaak we hem startten.
Aan het veld hingen twee aparte luisteraars, input en keyup. Een gewone
toets zet ze allebei aan. Dus bij elke letter draaide de volledige analyse
twee keer: 90 ms bij een wachtwoord van 16 tekens, een halve seconde bij 32.
En niets remde het af: geen timer, geen lengtelimiet, geen aparte thread. Vanaf een stuk of twintig tekens deed de browser er langer over om te analyseren dan jij om de volgende toets in te drukken. De aanslagen stapelden zich op in een rij die nooit meer leegliep, en de pagina reageerde niet meer terwijl je typte. Een lang wachtwoord plakken bevroor hem seconden.
De oplossing zijn twee kleine wijzigingen. We haalden de keyup-luisteraar weg,
want input dekt typen, plakken, wissen en het automatisch invullen van je manager
al: de helft van het werk verdwijnt zonder ook maar iets te veranderen aan wat je
ziet. En we voegden 150 milliseconden wachttijd toe: terwijl je typt wordt er
niets geanalyseerd, en er wordt geanalyseerd zodra je stopt. Niemand typt zo traag
dat hij het merkt.
Het resultaat is identiek. Barcelona1992$ zegt nog steeds hetzelfde: dat
«barcelona» in het woordenboek staat, dat 1992 op een jaartal lijkt, en dat het in
minder dan een seconde valt.
De omschrijving brak midden in de zin af in vijftien talen
Deze is dommer en gênanter.
De zin die in de zoekmachine onder de titel verschijnt — de meta description — was afgekapt in vijftien van de achttien talen van de checker. Alleen Spaans, Koreaans en Japans bleven heel. Iemand heeft hem ooit op lengte bijgeknipt, midden in een woord afgesneden, en daarna heeft niemand gekeken.
In drie talen viel de knip midden in een speciaal teken, dus wat we aan Google serveerden was dit:
| taal | wat we serveerden | wat het moest zijn |
|---|---|---|
| Portugees | …datas e padr&o |
padrões |
| Turks | …arıyor ve ger&cced |
gerçekten |
| Vietnamees | …chúng tôi tra mật kh&# |
mật khẩu |
Het opvallende is wáár het zat: op de best presterende pagina van de site. De checker converteert veel beter dan de generator, en deed dat met een kapotte omschrijving. Alle vijftien zijn nu compleet.
Meteen maar: in het Koreaans, Japans en Vietnamees is de merknaam van het begin naar het eind van de titel verhuisd. In de zoekresultaten staat het domein toch al apart, dus beginnen met «password.es ·» verspilde alleen de ruimte die het zwaarst telt.
Een bias van één op honderd miljoen
De generator kiest elk teken met crypto.getRandomValues(), de goede bron van
toeval in de browser. Maar om dat enorme getal om te zetten in «een positie in het
alfabet» gebruikten we een rest, en daar zit een klassieke val: 2³² is geen
veelvoud van 83, dus kwamen de eerste posities van het alfabet een piepklein
beetje vaker voor dan de laatste.
Hoe klein: bij een alfabet tot 89 tekens in de orde van één op honderd miljoen. Het verandert niets. Het maakt je wachtwoord niet meetbaar zwakker, bij lange na niet.
We hebben het toch opgelost, en het zijn drie regels: de 45 overtollige waarden van de 4.294.967.296 mogelijke worden weggegooid en er wordt opnieuw getrokken. Op een site over wachtwoorden is «het is verwaarloosbaar» geen goede reden om een bias te laten zitten als weghalen zo weinig kost.
Wat ontbrak: de browser de belofte laten afdwingen
We hebben een heel artikel over dat je wachtwoord hier niet weggaat. Dat klopte, en toch was het alleen een zin van ons: technisch hield niets een geïnjecteerd script tegen om iets anders te doen.
Nu draagt elke pagina een content-security-policy. De twee delen die ertoe doen:
- Er draait geen script dat er bij het bouwen niet in stond. Elk legitiem script is ondertekend met zijn vingerafdruk; een geïnjecteerd script komt niet overeen en de browser weigert het uit te voeren.
connect-src 'none'. De site doet geen enkel netwerkverzoek vanuit JavaScript, en dat dwingt nu de browser af. Mocht ooit iets proberen te versturen wat je typt, dan komt het er niet uit.
De belofte is geen uitspraak meer maar een regel.
Wat er nog steeds mis is
Update van 31 augustus. Alle drie de punten in dit onderdeel zijn opgelost op dezelfde dag dat dit artikel verscheen: de vijftien talen beschrijven nu de echte motor, van de twee schalen is er één gemaakt, en de FAQ van de checker legt het verhaal van de symbolen uit. Ze blijven staan, want het verslag van wat er mis was is meer waard dan het plaatje van een vlekkeloze site.
Hier verdient dit artikel zijn plek.
Vijftien talen beschrijven de checker nog als de oude motor. Hun tekst zegt dat
hij de klassieke PasswordMeter-logica nabootst. Niet meer: die motor is vervangen
omdat hij Wachtwoord1! heel sterk noemde en een wachtwoordzin van vijf woorden
liet zakken, precies andersom dus. Spaans, Koreaans en Japans zijn bijgewerkt; de
andere vijftien niet.
Er zijn twee verschillende schalen in hetzelfde huis. De voorpagina bepaalt de sterkte op bits; de checker bepaalt hem op de tijd die kraken zou kosten. Verschillende criteria, en nergens zeggen we het.
Generator en checker tellen niet dezelfde symbolen: de een gebruikt er 27, de ander gaat uit van 33. Het verschil is klein, maar het is er.
Geen van de drie is ernstig. Alle drie zijn schuld, en ze staan liever hier dan te wachten tot iemand anders ze vindt.
Het changelog van deze versie
Omdat de repository niet openbaar is, staat het changelog hier voluit. Versie 0.2.0, 31 augustus 2026:
Toegevoegd. Een content-security-policy per pagina, met de vingerafdruk van elk script. Het artikel over waar je opgeslagen wachtwoorden staan, in alle achttien talen. Een redactionele wachtrij die bepaalt wat er geschreven wordt, en die opdraagt niets te publiceren zodra hij leeg is.
Opgelost. De omschrijving van de checker, midden in de zin afgekapt in vijftien talen, drie ervan met een teken doormidden. Het bevriezen van het tabblad tijdens het typen. De bias in de trekking. En de ontbrekende foto bij het vorige artikel.
Gewijzigd. De titel van de checker in het Koreaans, Japans en Vietnamees. De uitrol, die nu afgaat wanneer de gebouwde site al bestaat in plaats van een minuut eerder. En de Vietnamese, Hindi en Arabische vertalingen, die een publicatie niet langer tegenhouden ook al blijven ze de zwakste.
De cijfers in dit artikel zijn gemeten, niet geschat: de zxcvbn-tijden met hetzelfde pakket dat de site serveert en wachtwoorden met woorden en vervangingen; de bias van de trekking, berekend over de 4.294.967.296 waarden van een 32-bits geheel getal. De motor van de checker is zxcvbn, opensource.
Foto van Ernandes Alves · Pexels