0%

Zustand und Identität

Bytes als Text mit Base64

Jeder kann sich bei deinem Server anmelden, und der Client trägt die Session, die dabei herauskommt. Bevor die Anmeldung ein Passwort prüfen kann, brauchen Benutzername und Passwort einen Weg, in einer Anfrage zu reisen. Ein Header ist der naheliegende Ort, und er ist ein enger. In Kapitel 2 hast du gesehen, dass ein Headerwert auf einer Zeile steht, also nie \r oder \n enthalten kann, und in Kapitel 6, dass http.server einen , der nicht in Latin-1 passt, nicht einmal schreibt. Ein Passwort ist weniger höflich. Es kann Buchstaben wie ü enthalten, die in zwei Bytes brauchen, einen Zeilenumbruch oder Bytes, die überhaupt kein Text sind.

Die Lösung ist, die Bytes in Text aus einer kleinen, sicheren Zeichenmenge zu verwandeln und diesen Text am anderen Ende wieder in dieselben Bytes zurückzuverwandeln. tut genau das, mit 64 Zeichen: A bis Z, a bis z, 0 bis 9, + und /, dazu = zum Auffüllen.

Drei Bytes werden zu vier Zeichen

Base64 liest die Bytes jeweils zu dritt. Drei Bytes sind 24 Bits, die es in vier Gruppen zu je 6 Bits schneidet. Sechs Bits können von 0 bis 63 zählen, also wählt jede Gruppe eines der 64 Zeichen: 0 ist A, 25 ist Z, 26 ist a, 51 ist z, 52 ist 0, und 62 und 63 sind + und /. Hier sind die zwei Bytes von b"ab". Lies das Bild von oben: dieselben Bits, zuerst in Achter und dann in Sechser geschnitten.

Die 16 Bits von a und b werden in die 6-Bit-Gruppen 24, 22 und 8 geschnitten, die zu Y, W und I werden, und = ersetzt die fehlende vierte Gruppe.

a ist das Byte 01100001 und b ist 01100010. In Sechser geschnitten sind diese Bits 011000, 010110 und 0010. Es sind nur 16 Bits, also wird die dritte Gruppe mit zwei Null-Bits zu 001000 aufgefüllt. Die drei Gruppen sind die Zahlen 24, 22 und 8, also die Zeichen Y, W und I, und ein = steht für die Gruppe, die ganz fehlt. b"ab" wird zu YWI=. Eine letzte Gruppe aus einem Byte bekommt ==. Je drei Bytes, oder die ein oder zwei am Ende übrig gebliebenen, werden zu vier Zeichen, also ist der Text etwa ein Drittel länger als die Bytes.

Pythons base64 übernimmt das Zählen. base64.b64encode(data) nimmt Bytes und gibt den Base64-Text zurück, als Bytes aus diesen 64 Zeichen; base64.b64decode(text) nimmt diesen Text, als Bytes oder als str, und gibt die ursprünglichen Bytes zurück.

Was gibt das aus?

import base64

print(base64.b64encode(b"ab"), base64.b64decode("YWI="))

bytes(16) sind sechzehn Null-Bytes. Was gibt das aus?

import base64

print(len(base64.b64encode(bytes(16))), len(base64.b64encode(b"abc")))

Jetzt ein ganzer Hin- und Rückweg mit Text, der kein reines ASCII ist. Kodiere den zuerst in UTF-8-Bytes, weil Base64 mit Bytes arbeitet, und dekodiere die Bytes am Ende wieder in einen String:

Try it

ë brauchte zwei Bytes, also ist zoë:x sechs Bytes lang, und sechs Bytes werden zu acht Zeichen ohne Auffüllen. Die zwei Bytes von ë sind jetzt einfache Buchstaben, also passt der Text in jeden Header. Die letzte Zeile gibt 16 8 aus: Zehn Bytes sind drei volle Gruppen und ein übriges Byte, und vier Bytes sind eine Gruppe und ein übriges Byte, wobei jeder Rest auf vier Zeichen aufgefüllt wird.

Strenges Dekodieren

b64decode ist standardmäßig nachsichtig. Zeichen außerhalb des Alphabets werden kommentarlos übersprungen:

Try it

Der erste Aufruf lässt das ! weg und gibt b'ana:b' zurück, als wäre nichts falsch. Mit validate=True löst jedes Zeichen außerhalb des Alphabets, auch ein Leerzeichen oder ein Zeilenumbruch, binascii.Error mit der Meldung Only base64 data is allowed aus. Fehlendes Auffüllen löst in beiden Fällen binascii.Error aus. binascii.Error ist eine Art von , also fängt except ValueError es ab, zusammen mit dem UnicodeDecodeError, das .decode("utf-8") auslöst, wenn die Bytes kein UTF-8 sind.

Ein Server, der Zugangsdaten prüft, will die strenge Form. Ein Header mit verirrten Zeichen wurde nicht von einem Client geschrieben, der sich an die Regeln hält, und ihn stillschweigend zu reparieren heißt, etwas anzunehmen, das niemand senden wollte.

base64.b64decode("YW5h!OmI=") gibt b'ana:b' zurück. Was ändert validate=True?

Kodieren ist kein Verschlüsseln

Sieh dir an, was das Dekodieren gebraucht hat: den Text, und sonst nichts. Es gibt keinen Schlüssel und kein Geheimnis. Jeder, der Base64-Text sieht, kann ihn mit einem Aufruf umkehren, genau wie du gerade. Base64 ändert, wie Bytes aufgeschrieben werden; es verbirgt nichts.

Das ist für die nächste Lektion wichtig. Die Basic-Anmeldung von sendet Benutzername und Passwort als Base64 in einem Header. Für jeden, der die Anfrage unterwegs lesen kann, ist dieser Header das Passwort in aller Offenheit. Nur eine verschlüsselte Verbindung, HTTPS, hält es privat. Ein Hash, wie die, die Kapitel 8 mit hashlib erzeugt, ist etwas anderes: Er macht aus Bytes einen kurzen Fingerabdruck, der sich nicht zurückverwandeln lässt.

Eine Anfrage trägt authorization: Basic YW5hOmNvcnJlY3QgaG9yc2U=. Wer kann das Passwort lesen?

Warum werden Benutzername und Passwort überhaupt kodiert?

Base64 taucht überall dort auf, wo Bytes als Text reisen müssen. Ein späterer Kurs in dieser Reihe, WebSockets with Python, ist geplant und wird es wieder verwenden, im Handshake, der eine HTTP-Verbindung in einen WebSocket verwandelt. Als Nächstes verwendet dein Server es, um Benutzername und Passwort aus einer Anmeldeanfrage zu lesen.