0%

Toestand en identiteit

Bytes als tekst met Base64

Iedereen kan op je server inloggen, en de client draagt de sessie die daaruit volgt. Voordat de login een wachtwoord kan controleren, hebben de gebruikersnaam en het wachtwoord een manier nodig om in een verzoek te reizen. Een header is de voor de hand liggende plek, en een smalle. In hoofdstuk 2 zag je dat een headerwaarde op één regel staat, dus hij kan nooit \r of \n bevatten, en in hoofdstuk 6 dat http.server niet eens een schrijft die niet in Latin-1 past. Een wachtwoord is minder beleefd. Het kan letters bevatten zoals ü die in twee bytes innemen, een regeleinde, of bytes die helemaal geen tekst zijn.

De oplossing is de bytes omzetten in tekst die uit een kleine, veilige set tekens bestaat, en die tekst aan de andere kant weer omzetten in dezelfde bytes. doet precies dat, met 64 tekens: A tot en met Z, a tot en met z, 0 tot en met 9, + en /, plus = als opvulling.

Drie bytes worden vier tekens

Base64 leest de bytes per drie. Drie bytes zijn 24 bits, die het in vier groepen van 6 bits knipt. Met zes bits kun je van 0 tot 63 tellen, dus elke groep kiest een van de 64 tekens: 0 is A, 25 is Z, 26 is a, 51 is z, 52 is 0, en 62 en 63 zijn + en /. Hier zijn de twee bytes van b"ab". Lees de afbeelding van boven: dezelfde bits, eerst in achten geknipt en daarna in zessen.

De 16 bits van a en b worden geknipt in groepen van 6 bits, 24, 22 en 8, die Y, W en I worden, en = vervangt de ontbrekende vierde groep.

a is de byte 01100001 en b is 01100010. In zessen geknipt zijn die bits 011000, 010110 en 0010. Er zijn maar 16 bits, dus de derde groep wordt met twee nulbits aangevuld tot 001000. De drie groepen zijn de getallen 24, 22 en 8, en dat zijn de tekens Y, W en I, en een = staat voor de groep die helemaal ontbreekt. b"ab" wordt YWI=. Een laatste groep van één byte krijgt ==. Elke drie bytes, of de een of twee die aan het eind overblijven, worden vier tekens, dus de tekst is ongeveer een derde langer dan de bytes.

De base64 van Python doet het tellen. base64.b64encode(data) neemt bytes en geeft de base64-tekst terug, als bytes die uit die 64 tekens bestaan; base64.b64decode(text) neemt die tekst, als bytes of als str, en geeft de oorspronkelijke bytes terug.

Wat drukt dit af?

import base64

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

bytes(16) is zestien nulbytes. Wat drukt dit af?

import base64

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

Nu een hele heen-en-terugreis met tekst die geen gewone ASCII is. Codeer de eerst naar UTF-8-bytes, omdat base64 op bytes werkt, en decodeer de bytes aan het eind weer naar een string:

Try it

ë nam twee bytes in, dus zoë:x is zes bytes, en zes bytes worden acht tekens zonder opvulling. De twee bytes van ë zijn nu gewone letters, dus de tekst past in elke header. De laatste regel drukt 16 8 af: tien bytes zijn drie volle groepen en één overgebleven byte, en vier bytes zijn één groep en één overgebleven byte, waarbij elke rest wordt aangevuld tot vier tekens.

Strikt decoderen

b64decode is standaard vergevingsgezind. Tekens buiten het alfabet worden zonder melding overgeslagen:

Try it

De eerste aanroep laat de ! vallen en geeft b'ana:b' terug, alsof er niets mis is. Met validate=True werpt elk teken buiten het alfabet, ook een spatie of een regeleinde, binascii.Error op met de melding Only base64 data is allowed. Ontbrekende opvulling werpt hoe dan ook binascii.Error op. binascii.Error is een soort , dus except ValueError vangt hem op, samen met de UnicodeDecodeError die .decode("utf-8") opwerpt als de bytes geen UTF-8 zijn.

Een server die inloggegevens controleert, wil de strikte vorm. Een header met verdwaalde tekens is niet geschreven door een client die de regels volgt, en hem stilletjes repareren betekent iets accepteren wat niemand bedoelde te sturen.

base64.b64decode("YW5h!OmI=") geeft b'ana:b' terug. Wat verandert validate=True?

Coderen is geen versleutelen

Kijk wat het decoderen nodig had: de tekst, en verder niets. Er is geen sleutel en geen geheim. Iedereen die base64-tekst ziet, kan die met één aanroep terugdraaien, precies zoals jij net deed. Base64 verandert hoe bytes worden opgeschreven; het verbergt niets.

Dat doet ertoe voor de volgende les. De Basic-login van stuurt de gebruikersnaam en het wachtwoord als base64 in een header. Voor iedereen die het verzoek onderweg kan lezen, is die header het wachtwoord in het volle zicht. Alleen een versleutelde verbinding, HTTPS, houdt het privé. Een hash, zoals de hashes die hoofdstuk 8 met hashlib maakt, is anders: die maakt van bytes een korte vingerafdruk die niet kan worden teruggedraaid.

Een verzoek draagt authorization: Basic YW5hOmNvcnJlY3QgaG9yc2U=. Wie kan het wachtwoord lezen?

Waarom codeer je de gebruikersnaam en het wachtwoord überhaupt?

Base64 duikt op overal waar bytes als tekst moeten reizen. Een latere cursus in deze reeks, WebSockets with Python, is gepland en zal het opnieuw gebruiken, in de handshake die een HTTP-verbinding in een WebSocket verandert. Hierna gebruikt je server het om een gebruikersnaam en wachtwoord uit een loginverzoek te lezen.