Kapitel 5
Signierte Tokens und geheime Schlüssel
Signiert ist nicht geheim
Deine sample_state() stehen:
"tokens": {
"tok-ana-writes-7Qx2": {"user": "ana", "can_write": True},
"tok-ben-reads-K9pz": {"user": "ben", "can_write": False},
},
Hier stimmen zwei Dinge nicht, und zusammen sind sie Befund F6, eine Zeile von SECURITY.md, die noch leer ist. Die Strings stehen im reading_server.py lesen kann, beide Tokens. Und sie funktionieren für immer: Nichts in ihnen und nichts an ihnen endet je. Ein dritter Preis ist weniger dringend. Um ein Token zu erkennen, muss der Server eine
Ein Token, das der Server nicht speichert
Angenommen, das Token selbst sagte, für wen es ist, und trüge einen Beweis, dass der Server es geschrieben hat. Dann bräuchte der Server keine Liste. Er würde das Token lesen, den Beweis prüfen und glauben, was das Token sagt.
Kapitel 2 hat diesen Beweis gebaut. Ein Tag, das mit hmac und einem Schlüssel erzeugt wird, den nur der Server hat, kann niemand sonst erzeugen. Ein Token kann also aus zwei Teilen mit einem Punkt dazwischen bestehen: einer Payload, einem kleinen
{"user": "ben", "can_write": false} the payload, JSON
|
| base64
v
eyJ1c2VyIjogImJlbiIsICJjYW5fd3JpdGUiOiBmYWxzZX0= . 9d64c048...5f5a2fdc
[------------- the payload text --------------] [---- the tag ----]
the tag is computed over exactly this text 64 hex characters;
with the server's key only the key holder
can compute them
Lies es von oben. Das JSON wird zu Base64-Text. Das Tag deckt diesen Text ab und sonst nichts. Das Token ist der Text, ein Punkt und das Tag. Bau eins:
Die erste Zeile ist das Token: eyJ1c2VyIjogImJlbiIsICJjYW5fd3JpdGUiOiBmYWxzZX0=, ein Punkt und ein Tag, das mit 9d64c048 beginnt. Die zweite Zeile ist 32 64: ein Schlüssel aus 32 Bytes und ein Tag aus 64 Hex-Zeichen. base64.urlsafe_b64encode ist das Base64, das du kennst, mit zwei vertauschten Zeichen, - statt + und _ statt /, sodass der Text auch in einer
Zwei Einzelheiten in diesem Block sind einfacher als in Wirklichkeit. Der Schlüssel ist lesbarer Text, damit der Block kurz bleibt; ein echter Schlüssel besteht aus 32 zufälligen Bytes aus secrets.token_bytes, wie in Kapitel 2. Und der Payload-Text endet mit =, dem Base64-Padding. Die nächste Lektion verwendet eine Hilfsfunktion, die es weglässt, weil ein Bearer-Token = nur ganz am Ende haben darf.
Jeder kann es lesen
Ben hat dieses Token. Das kann er damit ganz ohne Schlüssel tun:
Es gibt b'{"user": "ben", "can_write": false}' aus. Es war kein Schlüssel nötig, denn Base64 ist eine Kodierung, und eine Kodierung verbirgt nichts. Das hast du in HTTP with Python gesehen und wieder in Kapitel 2. Das Tag beweist, wer die Payload geschrieben hat. Es tut nichts dafür, die Payload privat zu halten.
Ein signiertes Token ist also kein geheimer Behälter. Der Besitzer kann es lesen, und jeder andere, der es unterwegs sieht, auch. Ein Passwort, ein Schlüssel oder irgendetwas anderes, das ein Fremder nicht lesen sollte, kommt nie in eine Payload.
Was gibt das aus?
import base64
print(base64.urlsafe_b64decode("eyJ1c2VyIjogImJlbiJ9"))
Niemand kann es ändern
Ben hätte gern, dass can_write wahr ist. Er schreibt eine neue Payload, kodiert sie und behält das alte Tag, weil er kein neues erzeugen kann. Der Server tut, was er immer tut: Er berechnet das Tag des Payload-Texts, den er bekommen hat, und vergleicht es mit dem Tag, das mitgekommen ist.
Es gibt False für das geänderte Token und True für das unberührte aus. Das Tag gehört zu genau den Bytes, die signiert wurden. Ändere ein Zeichen der Payload, und das alte Tag passt nicht mehr, und ohne den Schlüssel kann Ben das passende nicht berechnen.
Ben ändert in seiner Payload false in true und behält das alte Tag. Warum lehnt der Server das Token ab?
Was es kostet
Der Server führt keine Liste, also hat er nichts, was er durchstreichen könnte. Wenn eine Kopie eines Tokens geleakt wird, funktioniert die Kopie genau so lange wie das Original, und eine Abmeldung beendet keins von beiden, weil ein Token nie eine Session erwähnt. An einem einzelnen Token lässt sich nach der Ausgabe nichts mehr ändern.
Zwei Dinge können es trotzdem beenden. Das erste ist die Zeit: Ein Token, das seinen eigenen Ablauf trägt, hört auf zu funktionieren, wenn dieser Moment vorbei ist, deshalb hält dieses Kapitel Tokens kurzlebig. Die nächste Lektion fügt den Ablauf hinzu. Das zweite ist der Schlüssel: Wenn der Server seinen Schlüssel ersetzt, besteht jedes Token, das der alte Schlüssel signiert hat, sofort die Prüfung nicht mehr. Lektion 5 tut das mit Absicht. Ein echter Dienst kann außerdem eine Liste der Tokens führen, die er zurückgezogen hat, was einen Teil des Speichers zurückbringt, den ein signiertes Token vermeidet.
Anas Token wird von jemand anderem kopiert. Der Server speichert keine Tokens. Was beendet die Nutzung der Kopie?
Du wirst dem als JWT begegnen
Außerhalb dieses Kurses begegnest du dieser Idee unter dem Namen JSON Web Token, kurz JWT. Ein JWT ist ein signiertes Token in einem festen Format: drei Teile mit einem Punkt dazwischen. Eine seiner Signaturmethoden, HS256 genannt, ist das
Was davon darf in die Payload eines Tokens?
Das Token in diesen Blöcken hat denselben Fehler wie die festen Strings: Es läuft nie ab. Die nächste Lektion gibt ihm ein Ende, innerhalb des Teils, den das Tag abdeckt.