Vragen om van protocol te wisselen · oefening
De acceptwaarde berekenen
Je parser geeft de server nu een headers["sec-websocket-key"] de sleutel van de client als weggestripte Sec-WebSocket-Accept, berekend uit die sleutel. Deze les berekent hem, en RFC 6455 geeft je een exact antwoord om tegen te controleren.
Wat de waarde bewijst
RFC 6455 sectie 1.3 legt uit waarom de server überhaupt iets moet berekenen. De server moet de client laten zien dat hij deze Sec-WebSocket-Key heet. Omdat de client voor elk verzoek een nieuwe sleutel kiest, past geen opgeslagen of verzonnen antwoord. Dus als de waarde in het antwoord degene is die de client verwacht, weet de client dat zijn verzoek iets heeft bereikt dat het begreep.
Meer bewijst het niet. Het recept hieronder is openbaar en gebruikt niets geheims, dus iedereen kan de waarde voor elke sleutel berekenen. Het laat niet zien wie de server draait, het versleutelt niets, en het is geen wachtwoord. Het is een controle dat beide kanten hetzelfde protocol spreken.
Het recept
De RFC legt nog één ingrediënt vast, een string van 36 tekens die elke WebSocket-server ter wereld gebruikt, geschreven in sectie 1.3 van de RFC:
258EAFA5-E914-47DA-95CA-C5AB0DC85B11
Hij staat in ws_handshake.py als de constante GUID. De RFC koos hem omdat programma’s die WebSockets niet begrijpen hem waarschijnlijk niet gebruiken, en dat maakt het resultaat betekenisvol. De acceptwaarde is:
de sleuteltekst, met
GUIDerachter geplakt;gecodeerd naar bytes met
"ascii", omdat elk teken in beide ASCII is;gehasht met SHA-1, wat een digest van 20 bytes geeft;
geschreven in
, wat 28 tekens geeft; terug gedecodeerd naar een
str, omdat hij als tekst in een header gaat.
Beide gereedschappen ken je uit hashlib maakte in hoofdstuk 8 een vingerafdruk van een body, daar met hashlib.sha1: het neemt bytes aan en geeft een hash-.digest() de ruwe bytes is en .hexdigest() dezelfde waarde als hexadecimale tekst, twee tekens per byte. SHA-1 wordt niet meer vertrouwd voor beveiligingswerk, maar de RFC gebruikt het niet voor beveiliging; ze gebruikt het als een vast recept dat beide kanten kunnen volgen. base64 vervoerde in hoofdstuk 7 bytes als headertekst.
De digest en de hextekst zijn makkelijk te verwarren, dus vergelijk ze op een waarde die niets met WebSockets te maken heeft:
Het drukt 20 40 v5l2ih1SSK6sFm0W6rvnBjTvc4I= af. De 20 bytes van de digest worden 40 hexadecimale tekens, of 28 base64-tekens met één = als opvulling. De acceptwaarde is de base64 van de digest, de derde daarvan. De hextekst van 40 tekens is een andere waarde, en een client die hem vergelijkt met de 28 tekens die hij verwacht, weigert de handshake.
Wat drukt dit af?
import hashlib
sha = hashlib.sha1(b"x")
print(len(sha.digest()), len(sha.hexdigest()))
Tekst, geen gedecodeerde bytes
De sleutel is base64, en je decodeerde hem in les 1 om zijn 16 bytes te zien. Daardoor is het verleidelijk om de gedecodeerde bytes te hashen. De RFC doet dat niet: ze hasht de sleutel precies zoals de client hem stuurde, de 24 tekens van de headerwaarde zonder spaties eromheen, gevolgd door de GUID. Je parser heeft de waarde al gestript, dus key + GUID is zoals het er staat de juiste tekst.
Wat gaat er in SHA-1?
Voor de voorbeeldsleutel van de RFC, dGhlIHNhbXBsZSBub25jZQ==, is het resultaat de waarde die RFC 6455 afdrukt:
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Als je
Je opdracht
Schrijf in ws_handshake.py accept_value(key): geef de base64-tekst van de SHA-1-digest van key + GUID, gecodeerd als ASCII, terug als str. Twee regels zijn genoeg. Laat de functies voor les 4 tot en met 6 zoals ze zijn.
Druk op Run. De sectie van les 3 in handshake_demo.py drukt nu de acceptwaarde voor de sleutel van de RFC af, onder de dictionary uit les 2:
Lesson 3: accept_value
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Dat is een regel uit een lokale run van de afgewerkte les. Vergelijk hem, teken voor teken, met de waarde van de RFC hierboven. Als ze overeenkomen, druk je op Submit. De controle roept accept_value aan met de sleutel van de RFC en met sleutels die je nog niet hebt gezien, en ze controleert dat het resultaat tekst is, geen bytes.
De server kan nu zijn bewijs berekenen. De volgende les beslist wanneer hij het stuurt, en wat hij in plaats daarvan stuurt als het verzoek er geen is dat hij moet accepteren.
Opdracht
Schrijf accept_value(key) in ws_handshake.py.
Plak de constante GUID achter de sleuteltekst, codeer het resultaat met "ascii", neem de SHA-1-digest met hashlib.sha1(...).digest(), codeer de digest met base64.b64encode, en geef hem terug als str. Gebruik de sleutel precies zoals je hem ontvangt, als tekst; decodeer hem niet eerst.
Laat de s3pPLMBiTxaQ9kYGzzhZRbK+xOo= afdrukken, de
Druk daarna op Submit. De controle roept accept_value aan met de sleutel van de RFC en met drie sleutels die je nog niet hebt gezien, vergelijkt elk resultaat met de waarde die het recept van de RFC geeft, en controleert dat het resultaat een str is. Als een resultaat fout is, noemt de melding de stap waar het op wijst: de GUID, de vorm van de digest, de hash, of de gedecodeerde sleutel. Het importeren van ws_handshake.py mag niets afdrukken.