0%

Hashes, MACs und Zufallswerte · Übung

Eine Nachricht mit einem Schlüssel signieren

Angenommen, ein Server will einem Client eine kurze Nachricht geben, etwa „Buch 7 gehört ana“, und sie wiedererkennen, wenn sie zurückkommt. Die einfachste Idee ist, einen -Digest als Tag mitzuschicken und bei der Rückkehr zu prüfen, ob der Digest noch zur Nachricht passt. Hier ist dieser Entwurf, und ein Fälscher:

import hashlib

message = b"book 7 belongs to ana"
tag = hashlib.sha256(message).hexdigest()

forged = b"book 7 belongs to ben"
forged_tag = hashlib.sha256(forged).hexdigest()

# The server's check: digest the message that arrived,
# and compare it with the tag that arrived.
print(hashlib.sha256(forged).hexdigest() == forged_tag)

Es gibt True aus. Der Fälscher hat die Nachricht geändert, den Digest der neuen berechnet, und die Prüfung des Servers geht durch. Nichts im Tag hing von etwas ab, das dem Fälscher fehlt. Das ist der schwache Entwurf, und diese Lektion ersetzt ihn.

Ein Schlüssel

Die fehlende Zutat ist ein Geheimnis, das der Ersteller des Tags und sein Prüfer beide haben und sonst niemand: ein Schlüssel (key).

Ein solches Geheimnis hast du schon einmal erzeugt. In with Python kam eine aus secrets.token_urlsafe(16): 128 zufällige Bits vom , weil das random vorhersagbar ist. Ein Schlüssel ist dieselbe Idee mit zwei Unterschieden. Er besteht aus Bytes, nicht aus Text, und dieser Kurs verwendet 32 davon:

key = secrets.token_bytes(32)

Ein Schlüssel ist nie ein Text, den ein Mensch gewählt hat. Ein Wort oder ein Satz hat viel weniger Zufall, als seine Länge vermuten lässt, und ein Angreifer kann wahrscheinliche Formulierungen ausprobieren. Zweiunddreißig zufällige Bytes sind 256 zufällige Bits, so viele, wie SHA-256 ausgibt, und es gibt keine Abkürzung, um sie zu erraten. signing.py hält die Zahl 32 in einer Konstanten, KEY_BYTES.

Ein Schlüssel ist außerdem das Einzige in diesem Modul, das beim Server bleiben muss. Die Nachricht ist nicht geheim, und das Tag, das der Server mit ihr mitschickt, auch nicht. In diesem Kapitel lebt ein Schlüssel nur so lange wie ein Lauf eines Programms. Kapitel 5 behandelt, wo ein echter Dienst einen aufbewahrt.

hmac

Das Modul hmac der Standardbibliothek berechnet ein Tag mit Schlüssel. ist eine veröffentlichte Konstruktion, die einen Schlüssel in eine Hashfunktion einmischt. Mit SHA-256 heißt sie HMAC-SHA-256:

tag = hmac.new(key, message, hashlib.sha256).hexdigest()

Drei Dinge solltest du über diesen Aufruf wissen:

  • Schlüssel und Nachricht sind beide bytes. Ein str löst aus.

  • Das dritte nennt die Hashfunktion, und es ist Pflicht.

  • hexdigest() gibt das Tag als Text zurück: 64 hexadezimale Zeichen, unabhängig von der Länge der Nachricht. SHA-256 liefert 32 Bytes, und Hex schreibt jedes Byte als zwei Zeichen.

Mit demselben Schlüssel: Wie lang sind die zwei Tags von hmac.new(key, b"", hashlib.sha256).hexdigest() und hmac.new(key, b"a" * 300, hashlib.sha256).hexdigest()?

Durch Neuberechnen verifizieren

Ein Tag wird nicht dekodiert. Es steckt nichts darin, das man lesen könnte. Der Prüfer nimmt die Nachricht, die er bekommen hat, und seine eigene Kopie des Schlüssels, berechnet das Tag noch einmal und vergleicht das Ergebnis mit dem Tag, das er bekommen hat. Wenn sich die Nachricht geändert hat, das Tag sich geändert hat oder der Schlüssel ein anderer war, passen die beiden nicht zusammen.

verify(key, message, tag) ist also kurz: die Nachricht noch einmal signieren und vergleichen. Es gibt True oder False zurück und sonst nichts. Ein Tag, das zu kurz ist, leer ist oder mit einem anderen Schlüssel erzeugt wurde, ist kein Fehler. Es ist ein falsches Tag, und die Antwort ist False.

Vergleiche in dieser Lektion mit ==. Die nächste Lektion sieht sich genau diese eine Zeile an.

Bau keine eigene Konstruktion

hashlib.sha256(key + message) sieht nach derselben Idee aus: den Schlüssel voranstellen und hashen. Es ist nicht dasselbe. Diese Konstruktion hat mit SHA-256 eine bekannte Schwäche: Wer ein gültiges Tag sieht, kann ohne den Schlüssel ein gültiges Tag für eine längere Nachricht berechnen, die mit der ursprünglichen beginnt. HMAC wurde so entworfen, dass das nicht funktioniert. Verwende hmac.

Deine Aufgabe

Öffne signing.py. Seine drei sind Platzhalter, und main() ist bereit, sie zu benutzen. Schreib:

  1. new_key(), das secrets.token_bytes(KEY_BYTES) zurückgibt.

  2. sign(key, message), das das HMAC-SHA-256-Tag als 64 Hex-Zeichen zurückgibt.

  3. verify(key, message, tag), das True zurückgibt, wenn tag das Tag von message unter key ist, und sonst False.

Greif über die Module auf die Standardbibliothek zu, wie es die Importe oben in der Datei tun: secrets.token_bytes(...), hmac.new(...).

Führ dann die Datei aus:

python signing.py

Sie gibt key: 32 bytes aus, ein Tag, das bei jedem Lauf anders ist, weil der Schlüssel neu ist, unchanged message: True und changed message: False.

Lass jetzt das Kurs-Hilfsprogramm den Fälscher spielen:

python pl_sec.py forged-message

Der Lauf verwendet keinen Server und keinen : Er ruft deine drei Funktionen direkt auf. Er gibt eine Nachricht mit ihrem Tag aus und übergibt dann fünf Fälschungen an dein verify():

--- five forgeries, each handed to your verify() ---
the message changed, the old tag kept                        -> refused
the message changed, the tag is SHA-256 of the new message   -> refused
the right tag cut to its first 8 characters                  -> refused
an empty tag                                                 -> refused
a tag made with another key                                  -> refused
--- how verify() compared ---
hmac.compare_digest was not called: verify() compares some other way
--- result ---
5 of 5 forgeries refused

Die zweite Fälschung ist die, die den Digest-Entwurf oben in dieser Lektion getäuscht hat. Die Zeile unter how verify() compared ist heute so zu erwarten. Sie betrifft das == in deinem verify, und sie ändert sich in Lektion 4.

Ein Server signiert Nachrichten mit einem Schlüssel und prüft sie mit verify. Wer kann ein Tag erzeugen, das die Prüfung besteht?

Klicke dann auf Check my work. Dein Modul lehnt jetzt jede Fälschung ab, die das Hilfsprogramm versucht hat. Die nächste Lektion fragt, was ein Angreifer aus der Art lernen könnte, wie verify Nein sagt.

Aufgabe

Schreib in signing.py drei .

  • new_key() gibt secrets.token_bytes(KEY_BYTES) zurück.

  • sign(key, message) gibt hmac.new(key, message, hashlib.sha256).hexdigest() zurück. key und message sind bytes, und das Ergebnis ist Text aus 64 Hex-Zeichen.

  • verify(key, message, tag) berechnet das Tag von message unter key noch einmal und gibt True zurück, wenn es gleich tag ist, und sonst False. Vergleiche vorerst mit ==.

Greif über die auf die Standardbibliothek zu, wie es die Importe oben in der Datei tun.

Führ im python signing.py und python pl_sec.py forged-message aus.

Klicke dann auf Check my work. Es ruft new_key() mit einem Ersatz für secrets.token_bytes auf, der die verlangte Größe festhält, damit es erkennen kann, dass der Schlüssel das Ergebnis eines Aufrufs von secrets.token_bytes(32) ist, und es prüft, dass sich zwei echte Schlüssel unterscheiden. Es vergleicht sign() mit für Schlüssel und Nachrichten, die du noch nicht gesehen hast, darunter eine leere Nachricht und eine mit Buchstaben außerhalb von ASCII. Es ruft verify() mit dem richtigen Schlüssel, der richtigen Nachricht und dem richtigen Tag auf und mit einer geänderten Nachricht, einem geänderten Tag, einem anderen Schlüssel, einem gekürzten Tag, einem leeren Tag, einem verlängerten Tag und dem einfachen -Digest: Der erste Aufruf muss genau True ergeben und die anderen genau False. Wie verify() vergleicht, wird in dieser Lektion nicht geprüft. Nichts aus deinem Terminal wird bewertet.