Um einen Protokollwechsel bitten · Übung
Den Accept-Wert berechnen
Dein Parser übergibt dem Server jetzt ein headers["sec-websocket-key"] den Schlüssel des Clients als Sec-WebSocket-Accept, berechnet aus diesem Schlüssel. Diese Lektion berechnet ihn, und RFC 6455 gibt dir ein genaues Ergebnis, mit dem du vergleichen kannst.
Was der Wert beweist
RFC 6455, Abschnitt 1.3, erklärt, warum der Server überhaupt etwas berechnen muss. Der Server muss dem Client zeigen, dass er diesen Sec-WebSocket-Key irgendetwas zu berechnen. Weil der Client für jede Anfrage einen neuen Schlüssel wählt, passt keine gespeicherte oder erfundene Antwort. Wenn der Wert in der Antwort also der ist, den der Client erwartet, weiß der Client, dass seine Anfrage etwas erreicht hat, das sie verstanden hat.
Mehr beweist er nicht. Das Rezept unten ist öffentlich und verwendet nichts Geheimes, also kann jeder den Wert für jeden Schlüssel berechnen. Er zeigt nicht, wer den Server betreibt, er verschlüsselt nichts, und er ist kein Passwort. Er ist eine Prüfung, dass beide Seiten dasselbe Protokoll sprechen.
Das Rezept
Der RFC legt noch eine Zutat fest, einen 36 Zeichen langen String, den jeder WebSocket-Server der Welt verwendet, notiert in Abschnitt 1.3 des RFC:
258EAFA5-E914-47DA-95CA-C5AB0DC85B11
Er steht in ws_handshake.py als Konstante GUID. Der RFC hat ihn gewählt, weil Programme, die WebSockets nicht verstehen, ihn wahrscheinlich nicht verwenden, und genau das macht das Ergebnis aussagekräftig. Der Accept-Wert ist:
der Schlüsseltext, mit angehängtem
GUID;mit
"ascii"in Bytes kodiert, da jedes Zeichen in beiden ASCII ist;mit SHA-1 gehasht, was einen 20 Byte langen Digest ergibt;
in
geschrieben, was 28 Zeichen ergibt; zurück in einen
strdekodiert, weil er als Text in einen Header kommt.
Beide Werkzeuge kennst du aus hashlib hat in Kapitel 8 aus einem Body einen Fingerabdruck gemacht, dort mit hashlib.sha1 genauso: Es nimmt Bytes und gibt ein Hash-.digest() die rohen Bytes sind und dessen .hexdigest() derselbe Wert als hexadezimaler Text ist, zwei Zeichen pro Byte. SHA-1 gilt für Sicherheitszwecke nicht mehr als vertrauenswürdig, aber der RFC verwendet es nicht für Sicherheit; er verwendet es als festes Rezept, dem beide Seiten folgen können. base64 hat in Kapitel 7 Bytes als Header-Text transportiert.
Der Digest und der Hex-Text sind leicht zu verwechseln, also vergleiche sie an einem Wert, der nichts mit WebSockets zu tun hat:
Es gibt 20 40 v5l2ih1SSK6sFm0W6rvnBjTvc4I= aus. Die 20 Bytes des Digests werden zu 40 hexadezimalen Zeichen oder zu 28 base64-Zeichen mit einem = als Auffüllung. Der Accept-Wert ist das base64 des Digests, der dritte dieser Werte. Der 40 Zeichen lange Hex-Text ist ein anderer Wert, und ein Client, der ihn mit den 28 Zeichen vergleicht, die er erwartet, lehnt den Handshake ab.
Was gibt das aus?
import hashlib
sha = hashlib.sha1(b"x")
print(len(sha.digest()), len(sha.hexdigest()))
Text, keine dekodierten Bytes
Der Schlüssel ist base64, und du hast ihn in Lektion 1 dekodiert, um seine 16 Bytes zu sehen. Das verleitet dazu, die dekodierten Bytes zu hashen. Der RFC tut das nicht: Er hasht den Schlüssel genau so, wie der Client ihn gesendet hat, die 24 Zeichen des Header-Werts ohne umgebende Leerzeichen, gefolgt von der GUID. Dein Parser hat die Leerzeichen um den Wert bereits entfernt, also ist key + GUID so, wie es dasteht, der richtige Text.
Was geht in SHA-1 hinein?
Für den Beispielschlüssel des RFC, dGhlIHNhbXBsZSBub25jZQ==, ist das Ergebnis der Wert, den RFC 6455 abdruckt:
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Wenn deine
Deine Aufgabe
Schreibe in ws_handshake.py die Funktion accept_value(key): Gib den base64-Text des SHA-1-Digests von key + GUID, als ASCII kodiert, als str zurück. Zwei Zeilen genügen. Lass die Funktionen für die Lektionen 4 bis 6, wie sie sind.
Klicke auf Run. Der Abschnitt zu Lektion 3 in handshake_demo.py gibt jetzt den Accept-Wert für den Schlüssel des RFC aus, unter dem Dictionary aus Lektion 2:
Lesson 3: accept_value
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Das ist eine Zeile aus einem lokalen Lauf der fertigen Lektion. Vergleiche sie Zeichen für Zeichen mit dem Wert des RFC oben. Wenn sie übereinstimmen, klicke auf Submit. Die Prüfung ruft accept_value mit dem Schlüssel des RFC und mit Schlüsseln auf, die du nicht gesehen hast, und sie prüft, dass das Ergebnis Text ist, keine Bytes.
Der Server kann jetzt seinen Nachweis berechnen. Die nächste Lektion entscheidet, wann er ihn sendet und was er stattdessen sendet, wenn die Anfrage keine ist, die er annehmen sollte.
Aufgabe
Schreibe accept_value(key) in ws_handshake.py.
Hänge die Konstante GUID an den Schlüsseltext an, kodiere das Ergebnis mit "ascii", nimm den SHA-1-Digest mit hashlib.sha1(...).digest(), kodiere den Digest mit base64.b64encode und gib ihn als str zurück. Verwende den Schlüssel genau so, wie du ihn erhältst, als Text; dekodiere ihn nicht vorher.
Lass die s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ausgeben, den
Klicke dann auf Submit. Die Prüfung ruft accept_value mit dem Schlüssel des RFC und mit drei Schlüsseln auf, die du nicht gesehen hast, vergleicht jedes Ergebnis mit dem Wert, den das Rezept des RFC ergibt, und prüft, dass das Ergebnis ein str ist. Wenn ein Ergebnis falsch ist, nennt die Meldung den Schritt, auf den es hindeutet: die GUID, die Form des Digests, den Hash oder den dekodierten Schlüssel. Das Importieren von ws_handshake.py darf nichts ausgeben.