Zum Inhalt springen
Allin

Einen Anwendungs-Geheimschlüssel richtig erzeugen

Veröffentlicht am 12.8.2026 · 12 Min. Lesezeit · Text- & Sprach-Tools

Daniel Okonkwo

Daniel OkonkwoFront-end-Entwickler und Tech-Redakteur bei Allin

Web-Performance · Dateiformate

Anhand von 4 Quellen geprüft

Profil ansehen
Kurz gesagt

Drei Fragen entscheiden es. Erstens: Welche Zufallsquelle ruft er auf? Ein solider Generator ruft die kryptografische Zufälligkeit der Plattform auf — crypto.getRandomValues im Browser, secrets in Python, random_bytes in PHP. Ein zweifelhafter ruft Math.random auf, das ausdrücklich als für Sicherheit ungeeignet dokumentiert ist: V8 setzt es mit xorshift128+ um, und eine Handvoll aufeinanderfolgender Ausgaben genügt, um den inneren Zustand zu rekonstruieren und den Rest vorherzusagen. Dieses Werkzeug besteht: Sein randStr ruft rngInt auf, das ein Uint32Array mit crypto.getRandomValues füllt; der Math.random-Zweig läuft nur, wenn crypto fehlt, was kein Browser tut. Zweitens: Wie viel Entropie trägt die Ausgabe? Multipliziere die Länge mit dem Zweierlogarithmus der Alphabetgröße. Der Django-Zeichensatz ist hier genau die fünfzig Zeichen, die Djangos eigenes get_random_secret_key verwendet — Kleinbuchstaben, Ziffern und !@#$%^&*(-_=+) — ein 50 Zeichen langer Schlüssel trägt also 50 × log2(50) = 282 Bit, gegenüber den etwa 128 Bit, die üblicherweise als Untergrenze gelten. Die alphanumerische Option ergibt 297 Bit. Die WordPress-Seite erzeugt die acht wp-config-Konstanten mit 64 Zeichen aus einem 92-Zeichen-Satz, der Byte für Byte wp_generate_password mit beiden Sonderzeichen-Schaltern entspricht: je 418 Bit. Drittens: Wohin geht der Schlüssel danach? Keine der beiden Antworten hilft, sobald er in einen Chat, ein Ticket oder einen Commit eingefügt wurde — ein geteiltes Geheimnis ist ein verbrauchtes Geheimnis, und die einzige Abhilfe ist, ein neues zu erzeugen und zu wechseln.

Wie man einen soliden Geheimschlüssel-Generator von einem zweifelhaften unterscheidet, mit diesem als durchgerechnetem Beispiel: welche Zufallsquelle er aufruft, wie man die Entropie selbst berechnet und was am Tag des Schlüsselwechsels wirklich kaputtgeht.

Erste Frage: welche Zufallsquelle

Jede Sprache hat zwei Zufallszahlengeneratoren, und sie sind nicht austauschbar. Der eine ist schnell, aus einem Startwert deterministisch und für das Mischen einer Playlist oder das Streuen eines erneuten Versuchs gedacht. Der andere ist langsamer, schöpft aus dem Entropievorrat des Betriebssystems und ist für alles gedacht, was ein Angreifer gern erraten würde. In JavaScript sind das Math.random und crypto.getRandomValues; in Python random und secrets; in PHP mt_rand und random_bytes. Den ersten dort zu nehmen, wo der zweite hingehört, ist der häufigste Fehler eines Schlüsselgenerators.

Was Math.random disqualifiziert, ist nicht, dass die Ausgabe gemustert aussähe — tut sie nicht. Es ist, dass der Generator eine kleine deterministische Maschine mit rekonstruierbarem Zustand ist. V8, die Engine in Chrome und Node, verwendet xorshift128+, einen 128-Bit-Zustand, der durch Schiebungen und Exklusiv-Oder fortgeschrieben wird. Aus einer kurzen Folge aufeinanderfolgender Ausgaben lässt sich dieser Zustand rekonstruieren, und danach ist jeder künftige Wert bekannt und jeder vergangene nachspielbar. Die Engine ist auch nicht verpflichtet, ihn unvorhersehbar zu initialisieren, und Browser haben ihn historisch aus Dingen initialisiert, die ein Angreifer beobachten oder beeinflussen kann.

Dieses Werkzeug ruft den richtigen auf. Liest man den Code vom Knopf nach innen: Der Erzeugungs-Handler baut eine Zeichenkette Zeichen für Zeichen mit randStr, randStr wählt jeden Index mit rngInt, und rngInt legt ein Uint32Array mit einem Element an und füllt es mit crypto.getRandomValues. Es gibt einen Rückfall auf Math.random, aber er steht hinter einer Prüfung auf das Vorhandensein von crypto und crypto.getRandomValues, und getRandomValues ist in jedem Browsing-Kontext verfügbar, auch in unsicheren — anders als crypto.subtle. In der Praxis ist der Rückfall unerreichbarer Code.

Zweite Frage: wie viele Bit, und wie man sie selbst zählt

Die Rechnung passt in eine Zeile. Wird jedes Zeichen unabhängig und gleichverteilt aus einem Alphabet von n Zeichen gezogen, trägt jedes Zeichen log2(n) Bit, und ein Schlüssel der Länge L trägt L × log2(n). Das ist alles. Es gibt keinen Zuschlag für „enthält Sonderzeichen“ oder „sieht kompliziert aus“: Alphabetgröße und Länge sind die einzigen Eingaben, weshalb ein langer Kleinbuchstaben-Schlüssel einen kurzen voller Satzzeichen schlägt.

Wende sie auf die Voreinstellung an. Der Django-Zeichensatz besteht aus den sechsundzwanzig Kleinbuchstaben, den zehn Ziffern und den vierzehn Zeichen !@#$%^&*(-_=+) — fünfzig insgesamt, ohne Doppelung, was zählt, weil ein wiederholtes Zeichen das wirksame Alphabet stillschweigend verkleinern würde. log2(50) sind 5,644 Bit je Zeichen, der voreingestellte 50-Zeichen-Schlüssel trägt also 282,2 Bit. Die alphanumerische Option hebt das Alphabet auf zweiundsechzig und den Schlüssel auf 297,7 Bit; die volle Option auf sechsundsiebzig und 312,4 Bit. Alle drei sind riesig neben den 128 Bit, die üblicherweise als ausreichend genannt werden — dreiundzwanzig Zeichen des Django-Satzes würden sie schon überschreiten.

Die WordPress-Seite misst sich genauso. Sie gibt die acht wp-config-Konstanten aus — die vier Schlüssel und die vier Salts — mit je 64 Zeichen aus einem 92-Zeichen-Satz: die zweiundsechzig Buchstaben und Ziffern, dann !@#$%^&*() und schließlich -_ []{}<>~`+=,.;:/?|. Das ist Zeichen für Zeichen das, was WordPress' eigenes wp_generate_password mit beiden Sonderzeichen-Argumenten liefert, und 64 × log2(92) ergibt 417,5 Bit je Konstante. Zwei Einzelheiten dieses Satzes lohnen den Blick: Er enthält ein Leerzeichen, was zulässig und leicht überraschend ist, und er enthält weder einfaches Anführungszeichen noch Backslash — deshalb landet der erzeugte Wert ohne jedes Escaping in define('AUTH_KEY', '…');.

Der Modulo-Bias, ehrlich benannt

Es gibt einen Mangel in der Umsetzung, und er gehört genau benannt, denn eine vage Formulierung wäre alarmierender als der Mangel selbst. Um eine zufällige 32-Bit-Zahl in einen Index in ein 50-Zeichen-Alphabet zu verwandeln, nimmt der Code den Rest: Index gleich Wert modulo 50. Zwei hoch zweiunddreißig ist 4 294 967 296, und das lässt sich nicht durch fünfzig teilen. Der Rest ist sechsundvierzig, also lassen sich sechsundvierzig der fünfzig Zeichen aus 85 899 346 der möglichen Werte erzeugen und die anderen vier aus 85 899 345.

Die bevorzugten Zeichen sind also um ein Verhältnis von eins zu 85 899 345 wahrscheinlicher als die übrigen, etwa 1,2 zu hundert Millionen. Für den 92-Zeichen-Satz von WordPress ist der Rest zwölf und das Verhältnis etwa 2,1 zu hundert Millionen. Ein Generator, der das behöbe, nutzte Rückweisungsstichproben — neu ziehen, sobald der Wert in den ausgefransten Schwanz fällt — und gewänne einen Unterschied, den keine Messung an einem 50-Zeichen-Schlüssel je feststellen könnte. Das ist eine Fußnote, keine Warnung.

Der Bias verschwindet vollständig, wenn die Alphabetgröße eine Zweierpotenz ist, denn dann geht die Division auf — ein Alphabet mit 64 Zeichen oder 256 Werten hat gar keinen. Das ist der elegante Grund, warum ein Großteil des Schlüsselmaterials in Hexadezimal oder Base64 ausgedrückt wird: Die Arithmetik ist exakt, und es bleibt nichts zu diskutieren.

Wozu der Schlüssel dient, und was ein Wechsel deshalb zerstört

Djangos Dokumentation zählt genau auf, was von SECRET_KEY abhängt: jede Sitzung, außer man nutzt das Cache-Sitzungs-Backend und hat den voreingestellten Session-Auth-Hash überschrieben; jede in einem Cookie gespeicherte Nachricht; jeden Token zum Zurücksetzen des Passworts; und jede Nutzung des Signatur-Frameworks, die keinen eigenen Schlüssel übergibt. Die Folge steht auf derselben Seite: Sobald ein Schlüssel nicht mehr SECRET_KEY ist und nicht in SECRET_KEY_FALLBACKS steht, wird all das auf einen Schlag ungültig.

In der Praxis heißt das: Alle sind ausgeloggt und jeder Zurücksetzen-Link, der schon in einem Postfach liegt, funktioniert nicht mehr — genau das, was man nach einem Leck will, und genau das, was man an einem Dienstagnachmittag grundlos nicht will. SECRET_KEY_FALLBACKS ist der dokumentierte Weg, beides zu haben: den neuen Schlüssel in SECRET_KEY setzen, den alten lange genug in der Fallback-Liste behalten, bis laufende Sitzungen und Links verfallen sind, und ihn dann entfernen. Django sagt ausdrücklich, dass Benutzerpasswörter nicht aus dem Schlüssel abgeleitet werden und in keinem Fall betroffen sind.

WordPress bietet keine vergleichbare Schonfrist, und seine eigene Dokumentation sagt es in einem Satz: Man kann die Schlüssel jederzeit ändern, um alle bestehenden Cookies ungültig zu machen, und das heißt, dass sich jede Nutzerin neu anmelden muss. Es gibt keine Fallback-Liste; die Änderung ist sofort und vollständig. Dieselbe Seite merkt an, dass die vier Schlüssel erforderlich und die vier Salts nur empfohlen sind, weil WordPress Salts selbst erzeugt, wenn keine definiert sind — ein Grund mehr, alle acht einzufügen statt der Hälfte.

Wo ein gut erzeugter Schlüssel trotzdem verbrennt

Der Generator ist der leichte Teil. Fast jeder echte Vorfall beginnt danach. Ein Schlüssel landet in einer eingecheckten Konfigurationsdatei, in einem in ein Ticket eingefügten Screenshot, in einer Nachricht an eine Kollegin, in einem Build-Log, in einer Umgebungsvariablen, die ein Debug-Endpunkt ausgibt, oder in einer Container-Image-Schicht, die den Branch überlebt, aus dem sie stammt. Nichts davon ist ein kryptografisches Versagen; alles davon beendet die Brauchbarkeit des Schlüssels genauso vollständig.

Der Fall Versionskontrolle verdient einen eigenen Satz, weil der Instinkt falsch ist. Die Zeile zu löschen und die Löschung zu committen entfernt den Schlüssel nicht: Er steht weiter in der Historie, weiter in jedem gemachten Klon und weiter in jedem Fork und jedem Spiegel des Repositorys. Die Historie umzuschreiben hilft nur, wenn du zugleich jede bereits geholte Kopie ungültig machst, was du nicht kannst. Die einzig solide Antwort ist, den Schlüssel als öffentlich zu betrachten und zu wechseln.

Im Browser zu erzeugen ist völlig in Ordnung und besser, als es klingt. Der Schlüssel entsteht in der Seite, die du schon offen hast, aus der Zufälligkeit des Betriebssystems selbst, und er existiert nur in diesem Tab, bis du ihn kopierst. Nichts wird irgendwohin geschickt, weil nichts geschickt werden muss: fünfzig Indizes zu ziehen und fünfzig Zeichen zusammenzufügen sind ein paar Zeilen Code, und ein Server fügte nur einen weiteren, nicht einsehbaren Ort hinzu, an dem der Schlüssel existiert hätte.

Alle Optionen beider Generatoren, mit der Entropie als Länge × log2(Alphabet)
EinstellungAlphabetEntropie
Django-Standard, 50 Zeichen50 — Kleinbuchstaben, Ziffern, !@#$%^&*(-_=+)282,2 Bit — Byte für Byte Djangos eigenes get_random_secret_key
Django, alphanumerisch, 50 Zeichen62 — Buchstaben beider Schreibungen und Ziffern297,7 Bit — gefahrlos in einer Shell-Variablen, nichts zu quoten
Django, voller Satz, 50 Zeichen76 — der alphanumerische Satz plus die vierzehn Symbole312,4 Bit — 30 Bit mehr als der Standard, ohne praktischen Gewinn
Django, 100 Zeichen50 — derselbe Satz, doppelte Länge564,4 Bit — die Einstellung gibt es, den Bedarf nicht
WordPress voller Satz, 64 Zeichen, acht Konstanten92 — entspricht wp_generate_password mit beiden Schaltern417,5 Bit je; enthält ein Leerzeichen, kein Anführungszeichen, keinen Backslash
WordPress nur alphanumerisch, 64 Zeichen62 — Buchstaben und Ziffern381,1 Bit — 36 Bit weniger, und nirgends etwas zu escapen
Jeder Generator, der Math.random aufruftWas auch immer er angibtEffektiv null — der Zustand lässt sich aus wenigen Ausgaben rekonstruieren
Django-Secret-Key-GeneratorErzeuge einen kryptografisch sicheren Django-SECRET_KEY mit gewählter Länge und Zeichensatz.Tool ausprobieren

Häufige Fragen

Ist ein im Browser erzeugter Schlüssel so sicher wie einer vom Server?
Eher sicherer. crypto.getRandomValues schöpft aus derselben Betriebssystem-Entropie, die ein Server nutzen würde, die Zufälligkeit hat also dieselbe Güte. Der Unterschied ist der Weg des Schlüssels: In der Seite erzeugt, existiert er in einem Tab und geht nirgendwohin, bis du ihn kopierst, während ein serverseitiger Generator bedeutet, dass der Schlüssel auf einer Maschine existierte, die du nicht kontrollierst, in einem Prozess, den du nicht einsehen kannst, vielleicht in einer Logzeile, die du nie zu Gesicht bekommst. Lade die Seite neu oder schließe den Tab, und der Wert ist fort. Wovor ein Browser dich nicht schützen kann, ist die Zwischenablage und das, wohin du danach einfügst.
Wie lang muss ein Geheimschlüssel wirklich sein?
Nimm die Voreinstellung des Frameworks und denke nicht weiter darüber nach. Djangos eigene Funktion liefert 50 Zeichen, aus ihrem 50er-Alphabet also 282 Bit; WordPress gibt 64 Zeichen aus 92 aus, also 418. Die üblich genannte Schwelle für ein symmetrisches Geheimnis liegt bei etwa 128 Bit, und 23 Zeichen des Django-Alphabets überschreiten sie bereits. Die Mehrlänge kostet nichts und bringt nichts; die Einstellungen mit 80 oder 100 Zeichen gibt es der Vollständigkeit halber, nicht weil sie jemand braucht. Die Länge wird erst dann zum falschen Hebel, wenn das Alphabet winzig ist: ein 20-Zeichen-Schlüssel aus reinen Ziffern trägt 66 Bit, weniger als die Hälfte eines 20-Zeichen-Schlüssels aus dem Django-Satz.
Heißt der Modulo-Bias, dass ich diesen Generator nicht nutzen sollte?
Nein. Der Bias ist echt und beträgt beim Django-Alphabet 1,2 zu hundert Millionen: Sechsundvierzig der fünfzig Zeichen sind aus 85 899 346 der vier Milliarden möglichen 32-Bit-Werte erreichbar, die anderen vier aus 85 899 345. Über fünfzig unabhängige Ziehungen verteilt, verschiebt er die Entropie eines Schlüssels um eine Größe weit unterhalb jeder Einheit, die man drucken würde. Wofür der Bias wirklich taugt, ist als Merkmal beim Lesen fremder Generatoren: Ein nackter Rest sagt dir, dass niemand an Rückweisungsstichproben gedacht hat — ein Hinweis auf den restlichen Code —, und Rückweisungsstichproben sagen dir, dass jemand daran gedacht hat. Keines von beidem ist ein Grund, einen bereits 282 Bit tiefen Schlüssel abzulehnen.
Was geht in dem Moment kaputt, in dem ich den Schlüssel wechsle?
In Django: jede Sitzung, die nicht im Cache-Backend liegt, jede in einem Cookie gespeicherte Nachricht und jeder Token zum Zurücksetzen des Passworts, samt der Links, die schon in Postfächern liegen. Die Dokumentation sagt es klar und nennt die Abmilderung — den alten Schlüssel in SECRET_KEY_FALLBACKS verschieben, ihn lange genug dort lassen, bis laufende Sitzungen und Links von selbst verfallen, dann löschen. Benutzerpasswörter werden nicht aus dem Schlüssel abgeleitet und bleiben unberührt. In WordPress gibt es keine Fallback-Liste: Das Ändern einer der acht Konstanten macht jedes bestehende Cookie sofort ungültig, und die Dokumentation sagt wörtlich, dass sich alle Nutzer neu anmelden müssen. Plane einen Schlüsselwechsel für eine ruhige Stunde — außer du reagierst auf ein Leck, dann tu es jetzt und nimm die Abmeldungen hin.
Ich habe meinen Schlüssel nach git committet. Reicht es, die Zeile zu löschen?
Nein. Ein Commit, der eine Zeile löscht, lässt den vorigen Commit unangetastet, der Schlüssel steht also weiter in der Historie und weiter in jedem bestehenden Klon, Fork und Spiegel. Die Historie umzuschreiben entfernt ihn aus deiner Kopie und bei allen, die danach holen, aber nicht bei denen, die vorher geholt haben — und bei einem öffentlichen Repository muss man annehmen, dass dazu automatische Scanner gehören, die neue Commits genau darauf beobachten. Betrachte den Schlüssel als veröffentlicht: Erzeuge einen neuen, lege den alten in die Fallback-Liste, falls dein Framework eine hat, und entferne ihn nach dem Übergangsfenster. Mach das Leck danach strukturell unmöglich, indem du den Wert in einer Umgebungsvariablen oder einem Secret-Store hältst und nur eine Datei committest, die die Variable benennt.

Artikel, die dich interessieren könnten

Alle Ratgeber
AnleitungSo erstellst du ein starkes PasswortLänge schlägt Komplexität. Hier steht, warum eine Vier-Wort-Passphrase stark ist, warum du ein Passwort nie wiederverwenden solltest, und wie ein Manager es mühelos macht.ErklärungWas ist ein QR-Code?Ein QR-Code ist ein 2D-Barcode, den eine Kamera liest, um einen Link oder Text zu öffnen. Hier steht, was er ist, warum er so viel hält, sein Aufbau und ein Sicherheitshinweis.ErklärungWas ist eine Hash-Funktion? (MD5, SHA-256)Eine Hash-Funktion wandelt jede Eingabe in einen Fingerabdruck fester Größe. Hier steht, was sie tut, ihre Kerneigenschaften, gängige Anwendungen und welche Algorithmen sicher sind.ErklärungWas ist eine UUID (und wann nutzen)?Eine UUID ist ein 128-Bit-Bezeichner, der ohne zentrale Instanz eindeutig ist. Hier steht, wie sie aussieht, warum sie nützlich ist, die Versionen und wann man eine nutzt.RatgeberEine Datei verschlüsseln — und den Schlüssel auf einem anderen Weg schickenDas Verschlüsseln ist die leichte Hälfte. Hier steht genau, was das Browser-Werkzeug mit deiner Datei macht — Verfahren, Schlüsselableitung, Salt, Nonce — und warum ein verschlüsselter Anhang mit dem Passwort im selben Verlauf gar nichts schützt.RatgeberWas ein Passwortmanager nicht messen kannEntropie bepreist genau einen Angriff: Offline-Raten gegen einen gestohlenen Hash. Oberhalb von etwa 90 Bit entscheidet die Zahl nichts mehr — und die Anzeige dieser Seite hat ein zufälliges 20-Zeichen-Passwort in 300 von 300 Ziehungen zu niedrig bewertet.

Ähnliche Tools

Alles hier beschreibt, was diese vier Werkzeuge heute tun — geprüft, indem ihr eigener Code mit genau den in jedem Artikel abgedruckten Eingaben ausgeführt wurde — und nicht, was ein Standard ihnen vorschreibt. Wo ein Werkzeug einen Fall falsch behandelt, steht das klar da, statt umgangen zu werden, und nichts wurde geändert, damit ein Artikel sich besser liest. Zwei Folgerungen. Lass jede Umwandlung zuerst über eine Kopie laufen und vergleiche beide Enden: Ein Textwerkzeug, das etwas löscht, sagt es nicht. Und betrachte ein Geheimnis als geteilt, sobald es die Seite verlässt — es in einen Chat, ein Ticket oder ein Repository einzufügen verbrennt es, so gut es auch erzeugt wurde.

Quellen

Hast du einen Fehler in diesem Artikel entdeckt?