Das Symptom
Ein Kunde meldet, er habe einen fremden Gutschein einlösen können – er hatte nur
ein bisschen an seinem eigenen Code weitergezählt. Oder in der Datenbank fällt
auf, dass die Bestellkennungen einem Muster folgen, das ein aufmerksamer Mensch
in zehn Minuten erkennt. Oder, der schlimmste Fall: Jemand hat einen
Passwort-Rücksetz-Link für ein fremdes Konto benutzt, ohne je Zugriff auf dessen
Postfach gehabt zu haben.
Im Code steht an all diesen Stellen etwas völlig Unauffälliges: die
Zufallsfunktion, die jede Programmiersprache mitbringt.
Die „Layer 8“-Wahrheit
Es gibt zwei Sorten Zufall, und sie heißen fast gleich. Der eingebaute Generator
liefert Zahlen, die jeden statistischen Test bestehen – und die trotzdem restlos
vorhersagbar sind, weil sie aus einer festen Rechenvorschrift und einem inneren
Zustand entstehen. Die Python-Dokumentation schreibt das ungewöhnlich klar hin:
Der Mersenne Twister sei einer der am gründlichsten geprüften Generatoren
überhaupt und, weil vollständig deterministisch, für kryptografische Zwecke
vollkommen ungeeignet. In einer eigenen Warnung steht es noch einmal: nicht für
Sicherheitszwecke benutzen, dafür gibt es ein anderes Modul.
Der Layer-8-Anteil liegt in der Verwechslung von „sieht zufällig aus“ mit
„kann niemand erraten“. Verschärft wird sie durch einen zweiten, ebenso
verbreiteten Irrtum: Man startet den Generator mit der Uhrzeit und hält den
Startwert für ein Geheimnis. Ist er nicht – ein Sekundenwert hat pro Tag 86 400
Möglichkeiten, und der Erzeugungszeitpunkt steht im Zweifel als Anlagedatum
direkt neben dem Code in derselben Tabelle. Wer das Zeitfenster kennt, rechnet
die Codes nach, statt sie zu raten.
Die Reparatur ist unspektakulär und meist eine Zeile lang: der Generator, den die
Sprache ausdrücklich für Geheimnisse anbietet, der seine Bytes vom Betriebssystem
holt und keinen Startwert von außen annimmt. Zwei Dinge gehören trotzdem dazu.
Erstens genügend Länge, denn ein sicher erzeugter sechsstelliger Code ist immer
noch nur eine Million Möglichkeiten. Und zweitens: Alles, was mit dem alten
Generator schon ausgegeben wurde, bleibt vorhersagbar, bis es jemand ungültig
macht.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.Den Generator im eigenen Code identifizieren
Suche die Stelle, an der Codes, Kennungen, Rücksetz-Links, Gutscheine oder Losnummern entstehen. Wenn dort die allgemeine Zufallsfunktion der Sprache steht, ist das der Fund. Bekannte Namen dafür sind `random` in Python, `Math.random()` in JavaScript, `rand()` in C und PHP. Keiner davon ist für Geheimnisse gedacht, und keiner behauptet das von sich.
grep -rn "random\.\|Math\.random\|mt_rand\|rand()" --include=*.py --include=*.js .
2.Sich selbst vorführen, dass die Folge wiederholbar ist
Der eindrucksvollste Beweis ist der kürzeste: Derselbe Startwert liefert denselben Code, jedes Mal, auf jedem Rechner. Das ist keine Panne, sondern die dokumentierte Eigenschaft eines deterministischen Generators – und genau deshalb taugt er für Simulationen, bei denen man denselben Durchlauf wiederholen will.
python3 -c "
import random, string
A = string.ascii_letters + string.digits
random.seed(42); print(''.join(random.choices(A, k=12)))
random.seed(42); print(''.join(random.choices(A, k=12)))
"
# NbrnTP3fAbnF
# NbrnTP3fAbnF
3.Den Angriff nachbauen: das Zeitfenster durchprobieren
Wer den Generator mit der Uhrzeit startet, hat kein Geheimnis, sondern eine Rechenaufgabe verschenkt. Für jeden Sekundenwert im Verdachtsfenster wird derselbe Code erzeugt wie auf dem Server. Der Kandidat mit dem passenden Zeitstempel steht danach im Klartext da – hier die mittlere Zeile.
python3 -c "
import random, string
A = string.ascii_letters + string.digits
for k in range(1785999999, 1786000002):
random.seed(k); print(k, ''.join(random.choices(A, k=12)))
"
# 1785999999 N57zjNRdjV54
# 1786000000 tdlxaDHjEmy1
# 1786000001 kb6wBZwNsFlG
4.Auf den Generator für Geheimnisse umstellen
Die Umstellung ist meist eine Zeile. Das Modul `secrets` bringt fertige Funktionen für genau diese Fälle mit: eine Zeichenkette für Links und Token, eine Zahl unterhalb einer Obergrenze für Losnummern. Keinen Startwert setzen, keine eigene Zeichenauswahl basteln – die Voreinstellung ist hier die sichere.
python3 -c "
import secrets
print(secrets.token_urlsafe(16))
print(secrets.randbelow(1000000))
"
# G959cV50zlMd4D_VuCbGLA
# 245886
5.Auf der Kommandozeile aus derselben Quelle schöpfen
Wo kein Programm läuft, sondern nur ein Skript, holt man die Bytes direkt vom Betriebssystem. Dieselbe Quelle, die getrandom bedient, liegt unter Linux als Gerätedatei bereit. Damit lassen sich Anfangspasswörter oder Kennungen erzeugen, ohne einen eigenen Generator zu schreiben – jeder Aufruf liefert etwas anderes.
head -c 12 /dev/urandom | base64
# 8z9OsbNlDFlvVGSc
6.Die Länge nicht am Aussehen bemessen
Ein sicherer Generator nützt nichts, wenn der Code zu kurz ist: Sechs Ziffern sind eine Million Möglichkeiten und in Minuten durchprobiert, wenn niemand mitzählt. Für Rücksetz-Links und Einladungscodes gilt deshalb: reichlich lang, und zusätzlich eine Begrenzung der Fehlversuche sowie ein Ablaufdatum. Länge ersetzt keine Sperre, und eine Sperre ersetzt keine Länge.
7.Alte Codes für ungültig erklären, nicht nur den Generator tauschen
Wurden Rücksetz-Links, Sitzungskennungen oder Gutscheincodes mit dem falschen Generator erzeugt, ist der Austausch der Funktion nur die halbe Arbeit. Alles, was vorher ausgegeben wurde, bleibt vorhersagbar – noch offene Codes gehören entwertet und neu ausgestellt. Wer nur den Code ändert und die Altbestände laufen lässt, hat die Lücke dokumentiert, aber nicht geschlossen.