Hashes, MACs und Zufallswerte
Warum == der falsche Vergleich ist
Dein verify antwortet True oder False, und ein Angreifer, der ein falsches Tag sendet, erfährt nur, dass es falsch war. Angenommen, er erführe noch eine Sache mehr: wie viel Arbeit der Vergleich leisten musste, bevor er False gesagt hat. Diese Lektion zeigt, was dieses zusätzliche Stück
Ein Zählmodell
Hier ist ein von Hand ausgeschriebener Vergleich. Er geht zwei
Jeder Versuch außer dem letzten ist falsch, und die Antwort ist jedes Mal False. Die Zählung ist nicht jedes Mal gleich. 00000000 ist schon beim ersten Zeichen falsch, also wurde ein Zeichen angesehen. 7f3a0000 beginnt mit vier richtigen Zeichen, also ist die Zählung 5. Je besser der Versuch, desto höher die Zählung.
Das ist ein Modell. Pythons eigenes == auf zwei Strings prüft zuerst, ob sie gleich lang sind, und vergleicht dann ihren Speicher. Es führt keine Zählung, und auf dieser Website wird nichts gemessen. Was das Modell mit dem Original gemeinsam hat, ist die Form: Die Arbeit kann davon abhängen, wo der erste Unterschied liegt.
Was gibt examined("7f300000", secret) mit dem Geheimnis 7f3a91c2 zurück?
Was die Zählung wert ist
Ein Angreifer, der die Zählung sehen kann, muss nicht das ganze Geheimnis auf einmal raten. Er kann es Zeichen für Zeichen finden: jede der 16 Hex-Ziffern an der ersten Stelle ausprobieren und die behalten, bei der die Zählung steigt. Dann dasselbe für die zweite Stelle. recover tut das und benutzt nichts als das, was check meldet:
Es gibt ('7f3a91c2', 67) aus: das ganze Geheimnis nach 67 Versuchen. Ein falscher Buchstabe an Stelle p hält die Zählung bei p + 1 an, also bedeutet eine höhere Zählung einen richtigen Buchstaben. Ohne die Zählung muss ein Angreifer ganze Werte ausprobieren, und davon gibt es 16 ** 8, also 4.294.967.296. Die Zählung hat ein schweres Problem in acht leichte verwandelt. Für ein echtes Tag mit 64 Zeichen braucht dieselbe
Welches Geheimnis ist in verify gefährdet? Lektion 2 hat gesagt, dass ein Tag nicht geheim ist. Für eine Nachricht, die der Server signiert hat, stimmt das: Der Client hat beides. Das richtige Tag für eine Nachricht, die der Angreifer geschrieben hat, ist etwas anderes. Nur wer den Schlüssel hat, kann es berechnen, und verify berechnet es bei jedem Aufruf. Wenn der Vergleich zeigt, wie viel von einem Versuch richtig ist, kann der Angreifer dieses Tag Zeichen für Zeichen herausfinden und hält dann eine gefälschte Nachricht in der Hand, die die Prüfung besteht, ohne den Schlüssel.
Von einer Zählung zu einer Uhr
Ein echter Angreifer hat keine Zählung. Was er haben kann, ist die Zeit, die eine Antwort braucht, denn mehr Arbeit dauert länger. Dieses Signal ist winzig und im Rauschen vergraben, also braucht es sehr viele Anfragen und sorgfältige Statistik, um es zu lesen.
Du musst nicht beurteilen, wie praktikabel der Angriff gegen deinen Dienst ist, denn die Abwehr kostet eine Zeile. Dieses Muster lohnt sich zu behalten: Wenn das Schließen einer Schwachstelle billig ist, schließ sie, statt zu argumentieren, dass niemand sie ausnutzen würde.
hmac.compare_digest
Die Standardbibliothek hat einen Vergleich, der für Geheimnisse gemacht ist:
Es gibt True, False, False aus, wie es == täte. Der Unterschied liegt darin, wie es arbeitet: hmac.compare_digest sieht sich jedes Zeichen an, egal wie der Versuch aussieht, also verrät seine Arbeit nichts darüber, wo sich zwei Werte unterscheiden. Einen Vergleich mit dieser Eigenschaft nennt man einen Vergleich in konstanter Zeit (constant-time comparison).
Zwei Einzelheiten zählen, wenn du ihn verwendest:
Er kann trotzdem verraten, dass die beiden Werte verschieden lang sind, und seine Dokumentation sagt das. Für ein Tag ist das harmlos, denn jeder weiß, dass ein Tag 64 Zeichen hat.
Er akzeptiert zwei
str-Werte nur, wenn beide reines ASCII sind, und löst sonstaus. Ein Tag kommt von einem Client, und ein Client kann alles senden. Vergleiche also Bytes: Ruf zuerst auf beiden Seiten .encode("utf-8")auf.
Was kann hmac.compare_digest trotzdem verraten?
Wo die Regel gilt
Die Regel lautet: Wenn ein Geheimnis mit einem erwarteten Wert verglichen wird und die andere Seite den Versuch wählt, verwende hmac.compare_digest. In diesem Kurs ist das ein Tag, in Kapitel 3 ein Passwort-Hash und in Kapitel 8 noch ein Token.
Es ist nicht jedes Nachschlagen. state["sessions"].get(session_id) findet eine != der Anmeldung bei einem Passwort und nicht den Session-Speicher.
Welche Zeile braucht hmac.compare_digest?
Warum Schlüssel aus secrets kommen
Noch ein offenes Ende aus Lektion 2: Warum secrets.token_bytes und nicht das random?
Die zwei Zeilen sind dieselben 32 Zeichen, und die letzte Zeile gibt True aus. random ist dafür gebaut, wiederholbar zu sein: Vom selben Ausgangspunkt, dem Seed, erzeugt es immer dieselben Werte. Für eine Simulation oder ein Spiel ist das eine secrets fragt das
Ein Programm ruft random.seed(2026) und dann random.randbytes(16).hex() auf. Es wird morgen noch einmal ausgeführt. Was gibt der zweite Lauf aus?
Du hast jetzt den Grund für F2 und das Werkzeug, das ihn behebt. Das Projekt in der nächsten Lektion setzt hmac.compare_digest in verify ein und beweist es mit Tests.