Warum ein aus Math.random erzeugtes Passwort zufällig wirkt und trotzdem nicht kryptographisch sicher ist

Eine Seite hat gerade eine Zeichenfolge mit Groß- und Kleinbuchstaben, Ziffern und Symbolen ausgegeben. Das beschreibt höchstens den Zeichensatz. Es beweist nicht, dass die nächste Zeichenfolge unvorhersehbar ist. Vertrauen Sie nicht auf die Worte „zufällig“ oder „lokal“ auf der Seite. Öffnen Sie die DevTools: Der Generieren-Pfad sollte crypto.getRandomValues aufrufen, und das Passwort, das Sie gerade erzeugt haben, darf in Network nicht als Nutzdaten erscheinen.

Zuerst das Fazit: Math.random() ist ein gewöhnlicher Pseudozufallszahlengenerator. MDN schreibt, er liefere keine kryptographisch sicheren Zufallszahlen und dürfe nicht für sicherheitsrelevante Zwecke genutzt werden. Login-Passwörter, Einmal-Token und Schlüssel brauchen crypto.getRandomValues(). Eine zufällig wirkende Zeichenfolge, ein Generieren-Button, der offline noch funktioniert, und ein Schloss in der Adresszeile belegen jeweils eine andere Grenze. Keines davon beantwortet: „Welche API hat Generieren aufgerufen?“

Die Suchintention in einem Satz beantworten

Math.random() liefert eine Gleitkommazahl in [0, 1) mit annähernd gleichförmiger Verteilung. MDN ist eindeutig: Dieser Wert ist nicht kryptographisch sicher; nutzen Sie stattdessen Crypto.getRandomValues(). „Sieht zufällig genug aus“ beschreibt die Zeichenmischung im Output. Es sagt nichts darüber, ob eine Beobachterin, die einige Outputs sieht – oder den Seitenquelltext liest – den internen Zustand des Generators rekonstruieren und die nächsten Kandidaten ausgeben kann.

Diese Notiz richtet sich an Entwicklung und Betrieb, die einen „Online-Passwort-Generator“ prüfen, und an alle, denen ein Kollege einen Generieren-Tab geschickt hat, den sie vor der Nutzung selbst kontrollieren wollen. Sie ist keine Bedienungsanleitung für den Passwort-Generator und wiederholt nicht den Network-Durchgang zu „hat Klartext den Tab verlassen“. Mitnehmen sollten Sie die Schicht, die einen gewöhnlichen PRNG von einem CSPRNG trennt, warum ein naiver Modulo-Schritt selbst eine gute API verzerren kann, und den exakten Pfad vom Generieren-Klick durch den Call Stack.

Zufällig aussehen heißt nur: Die Zeichen können gemischt sein

„Zufällig“ mit dem Auge zu beurteilen ist billig und meist falsch. aB3$kP9x und Tr0ub4dor&3 sehen beide wie Passwörter aus. Das erste kann aus einem breiten Zeichensatz stammen. Das zweite kann eine merkbare Phrase mit üblichen Ersetzungen sein. Das Aussehen beantwortet „ähnelt das einem Passwort“, nicht „lässt sich das nächste Bit berechnen“.

Selbst wenn der Zeichensatz wirklich Groß- und Kleinbuchstaben, Ziffern und Symbole enthält, schrumpft eine vorhersagbare Indexfolge die Arbeit. Eine Angreiferin, die denselben Generator erneut ausführen kann – oder nach wenigen beobachteten Outputs den Zustand rekonstruiert – durchsucht Restkandidaten statt den vollen Raum. Bei einem Login-Passwort steht Unvorhersehbarkeit vor „es hat ein Sonderzeichen“.

Die Länge bleibt eine separate Frage. Derselbe CSPRNG bei 6 Zeichen und bei 16 Zeichen unterscheidet sich um Größenordnungen bei Brute-Force-Kosten. Unter 8 Zeichen ist der Raum auch bei der richtigen API klein. Der Zufallsmodus von FastPwd erlaubt 6–128 Zeichen, Standard ist 16; unter 8 wird als schwächer markiert – das ist eine Längenwarnung, kein schwächerer Algorithmus. Halten Sie die beiden Aussagen getrennt: Die API beantwortet Unvorhersehbarkeit; die Länge beantwortet den Suchraum.

Was für eine Zufallszahl Math.random tatsächlich ist

ECMAScript definiert Math.random() als Number größer oder gleich 0 und kleiner als 1, gezogen mit einem implementierungsdefinierten Algorithmus oder einer Strategie, die annähernd gleichförmig ist. Die Sprachspezifikation verlangt weder Vorhersagebeständigkeit noch, dass der Seed aus dem Entropie-Pool des Betriebssystems kommt. Eine Engine kann einen PRNG mit exzellenter Statistik und umkehrbarem Zustand wählen.

Die V8-Engine in Chrome nutzte bis Ende 2015 MWC1616. V8s eigener Beitrag dokumentiert die Grenzen: 64 Bit interner Zustand, verzerrte Qualität und Ausfälle in der TestU01-Suite. Ab V8 4.9.41 (mit Chrome 49) wechselte es zu xorshift128+: 128 Bit Zustand, Periode 2^128 − 1 und bestanden in TestU01. Firefox und Safari folgten später derselben Familie. Das sind statistische Upgrades, kein Sicherheits-Upgrade.

Derselbe V8-Text zieht die Grenze in einem Satz: xorshift128+ ist viel besser als die alte Implementierung und ist weiterhin nicht kryptographisch sicher. Hashing, Signaturen und Ver- sowie Entschlüsselung sind keine Jobs für einen gewöhnlichen PRNG; sie gehören an window.crypto.getRandomValues der Web Cryptography API. „Sieht statistisch zufällig aus“ und „eine Beobachterin kann spätere Outputs nicht billig rekonstruieren“ sind zwei verschiedene Hürden. Ein Passwort braucht die zweite.

GitHubs CodeQL-Abfrage Insecure randomness wertet Math.random zum Erzeugen eines Passworts als Befund – aus demselben Grund: Kann eine Angreiferin den Wert vorhersagen, kann sie sich als neuer Nutzer anmelden. Ein Generator, der nur Math.random() aufruft und auf einen Zeichensatz mappt, kann den Blicktest und einen groben Häufigkeitscheck bestehen und trotzdem die Prüfung scheitern, die voraussetzt, jemand habe den Quelltext gelesen oder einige Outputs abgefangen. Schreiben Sie nicht „es nutzte Math.random, aber der Zeichensatz ist breit“ als Bestanden.

Was getRandomValues tatsächlich ändert

crypto.getRandomValues(typedArray) füllt ein Integer-Typed-Array – Uint8Array, Uint32Array und die anderen Integer-Views – mit Werten, die im kryptographischen Sinn zufällig sind. MDN ist explizit zum Implementierungs-Kompromiss: Aus Performance-Gründen liest der Browser meist nicht bei jedem Bit eine Hardware-RNG. Er seedet einen für kryptographische Zwecke geeigneten PRNG aus einer OS-Entropiequelle wie Unix /dev/urandom oder der Windows-System-Zufallsschnittstelle. Der Algorithmus kann sich zwischen Browsern ändern. Das Designziel nicht: Veröffentlichte Outputs sollen es niemandem billig ermöglichen, den internen Zustand zu rekonstruieren.

Die beiden APIs leben nicht in derselben Spezifikation. Math.random ist ECMA-262. getRandomValues ist Web Cryptography. Ein einzelner Aufruf größer als 65.536 Bytes wirft QuotaExceededError; ein 16-Zeichen-Passwort liegt weit darunter. getRandomValues ist auch die einzige Crypto-Methode, die in einem unsicheren Kontext laufen darf; Key Derivation und subtle.encrypt brauchen weiterhin einen sicheren Kontext (HTTPS auf einer Produktionsseite). „Es funktioniert noch auf HTTP“ ist kein Grund, TLS wegzulassen.

Trennen Sie „dieser Tab hat die Zufallsbytes gezogen“ von „der Server hat gezogen und sie zurückgeschickt“. Selbst wenn der Server einen hochwertigen CSPRNG nutzte, lag das Passwort bereits im Speicher, in Logs und in diesem HTTP-Austausch der anderen Seite – als Klartext oder als etwas Reversibles. Das Schloss in der Adresszeile verschlüsselt nur den Hop von diesem Browser zum aktuellen TLS-Endpunkt. Es heißt nicht, der Endpunkt könne den Body nicht lesen. Behauptet die Seite „lokal erzeugt“, soll Generieren das Passwort nicht per POST hinausschicken und zurückechoen.

Was Sie sahen Was das stützen kann Was das nicht stützen kann
Der Output wirkt zufällig Der Zeichensatz kann breit sein Der Prozess ist unvorhersehbar
Quelltext enthält Math.random Die Seite nutzt irgendwo einen gewöhnlichen PRNG Generieren nutzt ihn für das Passwort
Quelltext enthält getRandomValues Die Seite importiert eine CSPRNG-API Dieser Klick nutzte diese API
Generieren funktioniert offline noch Dieser Klick wartete nicht auf eine Live-Minting-API Die Quelle ist nicht Math.random
Network-Suche findet das Passwort nicht Diese Beobachtung hat es nicht als Nutzdaten hochgeladen Die Zufalls-API ist ein CSPRNG

Modulo-Bias: Ein CSPRNG kann trotzdem verzerrt sein

Der zweite häufige Fehler passiert, nachdem das Skript bereits getRandomValues aufgerufen hat. Es nimmt ein Byte in 0–255 und schreibt charset[byte % charset.length]. Teilt die Zeichensatzlänge 256 nicht, bekommen die ersten Zeichen einen Extra-Anteil des Rests und erscheinen etwas häufiger. Bei einem 16-Zeichen-Passwort ist die Verzerrung meist klein, zeigt aber, dass die Implementierung „gleichförmig“ nicht fertig dachte. Ein fremder Zeichensatz oder das Mapping von 8 Bit auf eine große Wortliste macht die Verzerrung leichter sichtbar.

Rejection Sampling behebt das: Berechnen Sie das größte Vielfache der Zeichensatzgröße, das noch in 256 (oder 2^32) passt, verwerfen Sie jeden Zug ab diesem Limit, dann Modulo. Zufallszeichen und lesbare Passphrasen von FastPwd rufen beide crypto.getRandomValues auf, ziehen eine 32-Bit-Ganzzahl mit Uint32Array(1), setzen das Limit auf Math.floor(0x100000000 / max) * max und schleifen, wenn der Zug im Rest liegt. Fehlt getRandomValues, wird die Erzeugung abgelehnt – es gibt keinen Fallback auf Math.random. Prüfen Sie andere Sites weiter bis „Ganzzahl ziehen → Rest verwerfen → Zeichen mappen“. Den Funktionsnamen zu finden, ist nicht das Ende der Prüfung.

Testen Sie nicht mit einem echten Login-Passwort, einem Produktions-API-Key oder einem ungeschwärzten Ticket. Erzeugen Sie eine Wegwerf-Zeichenfolge und verwerfen Sie sie. Sie prüfen den Generieren-Pfad und den Traffic – Sie sollen kein neues Geheimnis ein zweites Mal preisgeben.

Jetzt prüfen: Quelltext, Konsole, Network

Ein Slogan ist kein Beweis. Bereiten Sie eine Markierung vor, die nie in echtem Traffic vorkommt: Länge auf 16 setzen, einmal generieren und die volle Zeichenfolge als Canary behandeln. Die drei Schritte unten enden in diesem Tab. Kein Konto nötig.

  1. DevTools Sources öffnen oder Seitenquelltext anzeigen. Getrennt nach Math.random und getRandomValues suchen. Dateien und Funktionsnamen notieren. Noch nicht entscheiden.
  2. Vom Klick-Handler des Generieren-Buttons nach unten lesen, bis Zeichen zum Ergebnis konkateniert werden. Dieser Pfad sollte crypto.getRandomValues aufrufen. Sehen Sie nur Math.random(), Date.now() oder einen festen Seed, ist diese Erzeugung kein CSPRNG.
  3. Zu Network wechseln, Preserve log aktivieren und Filter auf All lassen. Noch einmal generieren. Die volle Zeichenfolge in die Suchbox einfügen. Anfragezeile, Query-String, Request-Body und Analytics-Payloads dürfen dieses Passwort nicht enthalten.

Die Konsole beweist nur, dass die API existiert

In der Konsole liefert typeof crypto.getRandomValues === "function" mit true nur: Dieses Dokument exponiert die Methode im Browser. Es heißt nicht, das Seiten-Skript habe sie aufgerufen. Eine Zeile getRandomValues selbst auszuführen und dann das Passwort der Seite als „schon geprüft“ zu behandeln, sind zwei unabhängige Snippets.

Offline beweist nur, dass nichts remote gemint wurde

Lassen Sie die Seite fertig laden. Setzen Sie Network auf Offline oder trennen Sie das Systemnetz und generieren Sie erneut. Erscheint trotzdem ein Ergebnis, hing dieser Klick nicht von einer Live-API ab. Das ist ein starkes Signal für „in diesem Tab erzeugt“. Math.random braucht ebenfalls kein Netz. Offline ist ein Bestanden, das Sie weiter mit Quellpfad und Canary-Suche paaren müssen.

Gebündelte Skripte quetschen Namen oft auf einen Buchstaben. Taucht getRandomValues nicht auf, suchen Sie crypto und Uint32Array und beobachten Sie, ob Generieren einen neuen Business-POST auslöst. Minifizierung ist kein Freibrief. Sie bedeutet nur, dass die Suche einen Extra-Schritt braucht.

Drei Arten, „zufällig“ zu überinterpretieren

Erstens: Das Schloss in der Adresszeile als Beweis behandeln, der Generator sei ein CSPRNG. HTTPS/TLS schützt den Hop von diesem Browser zum aktuellen TLS-Endpunkt. Ein Server, der ein per POST empfangenes Passwort sieht, zeigt trotzdem ein Schloss. Das Schloss beantwortet nicht Math.random versus getRandomValues.

Zweitens: „Das Bundle erwähnt getRandomValues“ als Beweis, Generieren habe einen CSPRNG genutzt. Dieselbe Datei kann Web Crypto für Dateiverschlüsselung, einen Burn-Link oder eine Demo nutzen, während der Passwort-Pfad weiter Math.random aufruft. Umgekehrt widerlegt Math.random in einer Werbung oder Animation nicht den Passwort-Pfad. Der Klick-Stack ist der Beweis.

Drittens: „Es wurde nicht hochgeladen“ als „es ist unvorhersehbar“ lesen. Ein sauberes Network-Panel heißt nur: In dieser Beobachtung verließ das Passwort den Tab nicht als HTTP-Nutzdatenfeld. Eine Erweiterung kann trotzdem das DOM lesen. Die Zwischenablage des Betriebssystems kann die kopierte Zeichenfolge behalten. Das sind andere Grenzen. Zufallsqualität, ob es hochgeladen wurde und ob nach dem Verlassen noch eine Kopie irgendwo liegt, sind drei getrennte Fragen.

FAQ

Wenn ein erzeugtes Passwort zufällig aussieht – kann ich es als Login-Passwort nutzen?

Nicht allein wegen des Aussehens. Gemischte Zeichen deuten höchstens auf einen breiten Zeichensatz. Sie beweisen nicht, dass der Prozess unvorhersehbar ist. MDN schreibt, Math.random darf nicht für Sicherheit genutzt werden. Prüfen Sie außerdem, ob die Länge reicht, ob Sie die Zeichenfolge auf anderen Sites wiederverwendet haben und ob Generieren crypto.getRandomValues aufruft.

Wenn die Seite offline noch ein Passwort erzeugt – ist die Zufälligkeit sicher?

Das beweist nur, dass dieser Klick nicht auf eine Live-API wartete und der Server die Zeichenfolge nicht für Sie mintete. Math.random läuft ebenfalls offline. Nach erfolgreichem Offline-Test sollte der Quellpfad trotzdem getRandomValues zeigen, und Network darf das gerade erzeugte Passwort nicht enthalten.

Im Seitenquelltext stehen Math.random und getRandomValues – kann ich die Seite trotzdem nutzen?

Es hängt davon ab, welche Funktion der Generieren-Button aufruft. Animationen und Mischreihenfolgen dürfen Math.random nutzen. Passwörter, Token und Schlüssel müssen getRandomValues nutzen. Öffnen Sie Sources und lesen Sie vom Klick-Handler nach unten. Stoppen Sie nicht bei der Häufigkeit jedes Namens im Bundle.

Welche Zufalls-API nutzt FastPwd – und wird das Passwort hochgeladen?

Zufallszeichen und lesbare Passphrasen rufen beide crypto.getRandomValues auf und mappen eine 32-Bit-Ganzzahl per Rejection Sampling auf den Zeichensatz, sodass der Modulo-Rest verworfen wird. Fehlt die API, wird die Erzeugung abgelehnt. Länge 6–128 Zeichen, Standard 16; unter 8 wird als schwächer markiert. Das Ergebnis wird standardmäßig nicht hochgeladen. Sofort nutzbar – kein Konto.

Dieselbe Wegwerf-Zeichenfolge in einem sofort nutzbaren Tool durchspielen

Wenn Sie eine Seite wollen, die die Zufallsgrenze offen schreibt, öffnen Sie den Passwort-Generator von FastPwd. Keine Registrierung. Zufallsmodus auf Standard 16 Zeichen lassen und eine Wegwerf-Zeichenfolge erzeugen. Wiederholen Sie Abschnitt fünf: In Sources vom Generieren bis getRandomValues und der Rejection-Sampling-Schleife lesen; in Network die volle Zeichenfolge suchen, sodass Anfragezeile und Body fehlen.

Danach, wenn Sie wissen wollen, ob die Zeichenfolge einem weit missbrauchten Passwort ähnelt, nehmen Sie eine andere Wegwerf-Zeichenfolge in Passwortstärke prüfen und gleichen Sie gegen die öffentliche Häufigkeitsliste ab. Das geprüfte Passwort wird nicht hochgeladen. Ein Nichttreffer beweist nicht „nie im Web gesehen“; ein Treffer heißt nur: zu häufig. Das ist die Grenze der schwachen-Passwort-Liste, nicht die des CSPRNG. Fügen Sie kein Passwort ein, das Sie gleich aktivieren wollen, in irgendeinen Online-Checker ein.

Vor dem nächsten unbekannten Generator-Tab drei Fakten behalten. Zufällig aussehen beweist keine Unvorhersehbarkeit. Math.random kann statistische Test-Suiten bestehen und trotzdem kein CSPRNG sein. getRandomValues im Bundle zu sehen reicht nicht: Sie brauchen den Pfad, den Sie geklickt haben, und eine Canary, dass das Passwort nicht als Nutzdaten hinausging. Wiederholbare Beobachtungen gehören auf Ihre eigene Review-Checkliste.

Weiterlesen

„Lokale Verschlüsselung, nichts hochgeladen“ ist ebenfalls kein Beweis – suchen Sie in Network mit einer Canary in So prüfen Sie, dass browserlokale Verschlüsselung keinen Klartext sendet. Eine lokale Liste schwacher Passwörter kann gängige Passwörter finden und ist kein netzweiter Lookup; siehe Was eine lokale Liste geleakter Passwörter beweisen kann – und warum das kein netzweiter Abgleich ist. Wenn Sie das Passwort später einmal an eine Kollegin weitergeben, steht in Warum sich ein Schlüssel hinter der URL-Raute eignet – und wann der Schutz fällt, warum der Teil nach # standardmäßig nicht in HTTP geht.