Hashes, MAC's en willekeurige waarden · oefening
Een bericht ondertekenen met een sleutel
Stel dat een server een client een kort bericht wil geven, zoals “boek 7 is van ana”, en het wil herkennen als het terugkomt. Het eenvoudigste idee is om een
import hashlib
message = b"book 7 belongs to ana"
tag = hashlib.sha256(message).hexdigest()
forged = b"book 7 belongs to ben"
forged_tag = hashlib.sha256(forged).hexdigest()
# The server's check: digest the message that arrived,
# and compare it with the tag that arrived.
print(hashlib.sha256(forged).hexdigest() == forged_tag)
Het drukt True af. De vervalser veranderde het bericht, berekende de digest van het nieuwe bericht, en de controle van de server slaagt. Niets in de tag hing af van iets wat de vervalser niet heeft. Dat is het zwakke ontwerp, en deze les vervangt het.
Een sleutel
Het ontbrekende ingrediënt is een geheim dat de maker van de tag en de controleur allebei hebben, en dat niemand anders heeft: een sleutel.
Zo’n geheim heb je al eerder gemaakt. In secrets.token_urlsafe(16): 128 willekeurige bits van het random voorspeld kan worden. Een sleutel is hetzelfde idee met twee verschillen. Het zijn bytes, geen tekst, en deze cursus gebruikt er 32:
key = secrets.token_bytes(32)
Een sleutel is nooit tekst die een mens gekozen heeft. Een woord of een zin heeft veel minder willekeur dan zijn lengte doet vermoeden, en een aanvaller kan waarschijnlijke zinnen proberen. Tweeëndertig willekeurige bytes zijn 256 willekeurige bits, evenveel als SHA-256 oplevert, en er is geen kortere weg om ze te raden. signing.py bewaart het getal 32 in een constante, KEY_BYTES.
Een sleutel is ook het enige in deze module dat bij de server moet blijven. Het bericht is niet geheim, en de tag die de server ermee meestuurt evenmin. In dit hoofdstuk leeft een sleutel maar zo lang als één run van een programma. Hoofdstuk 5 behandelt waar een echte dienst er een bewaart.
hmac
De module hmac van de standaardbibliotheek berekent een tag met sleutel.
tag = hmac.new(key, message, hashlib.sha256).hexdigest()
Drie dingen om te weten over deze aanroep:
De sleutel en het bericht zijn allebei
bytes. Eenstrgeeft een. Het derde
noemt de hashfunctie, en het is verplicht. hexdigest()geeft de tag terug als tekst: 64 hexadecimale tekens, hoe lang het bericht ook is. SHA-256 geeft 32 bytes, en hex schrijft elke byte als twee tekens.
Met dezelfde sleutel, hmac.new(key, b"", hashlib.sha256).hexdigest() en hmac.new(key, b"a" * 300, hashlib.sha256).hexdigest(): hoe lang zijn de twee tags?
Verifiëren door opnieuw te berekenen
Een tag wordt niet gedecodeerd. Er zit niets in om te lezen. De controleur neemt het bericht dat hij ontving en zijn eigen kopie van de sleutel, berekent de tag opnieuw, en vergelijkt het resultaat met de tag die hij ontving. Als het bericht veranderd is, de tag veranderd is, of de sleutel een andere was, komen de twee niet overeen.
Dus verify(key, message, tag) is kort: onderteken het bericht opnieuw, en vergelijk. Hij geeft True of False terug en verder niets. Een tag die te kort is, leeg is, of met een andere sleutel gemaakt is, is geen fout. Het is een verkeerde tag, en het antwoord is False.
Vergelijk in deze les met ==. De volgende les kijkt kritisch naar die ene regel.
Bouw het niet zelf
hashlib.sha256(key + message) lijkt hetzelfde idee: zet de sleutel ervoor en hash. Het is niet hetzelfde. Die constructie heeft met SHA-256 een bekende zwakte: iemand die één geldige tag ziet, kan een geldige tag berekenen voor een langer bericht dat met het oorspronkelijke begint, zonder de sleutel te kennen. HMAC is zo ontworpen dat dit niet werkt. Gebruik hmac.
Je opdracht
Open signing.py. De drie main() staat klaar om ze te gebruiken. Schrijf:
new_key(), diesecrets.token_bytes(KEY_BYTES)teruggeeft.sign(key, message), die de HMAC-SHA-256-tag als 64 hextekens teruggeeft.verify(key, message, tag), dieTrueteruggeeft alstagde tag vanmessageonderkeyis, en andersFalse.
Bereik de standaardbibliotheek via haar modules, zoals de imports boven aan het bestand doen: secrets.token_bytes(...), hmac.new(...).
Voer het bestand daarna uit:
python signing.py
Het drukt key: 32 bytes af, een tag die bij elke run anders is omdat de sleutel nieuw is, unchanged message: True en changed message: False.
Laat nu het hulpprogramma van de cursus de vervalser spelen:
python pl_sec.py forged-message
De run gebruikt geen server en geen verify():
--- five forgeries, each handed to your verify() ---
the message changed, the old tag kept -> refused
the message changed, the tag is SHA-256 of the new message -> refused
the right tag cut to its first 8 characters -> refused
an empty tag -> refused
a tag made with another key -> refused
--- how verify() compared ---
hmac.compare_digest was not called: verify() compares some other way
--- result ---
5 of 5 forgeries refused
De tweede vervalsing is degene die het digest-ontwerp boven aan deze les om de tuin leidde. De regel onder how verify() compared is vandaag te verwachten. Hij gaat over de == in je verify, en hij verandert in les 4.
Een server ondertekent berichten met een sleutel en controleert ze met verify. Wie kan een tag maken die geverifieerd wordt?
Klik daarna op Check my work. Je module weigert nu elke vervalsing die het hulpprogramma probeerde. De volgende les vraagt wat een aanvaller zou kunnen leren van de manier waarop verify nee zegt.
Opdracht
Schrijf in signing.py drie
new_key()geeftsecrets.token_bytes(KEY_BYTES)terug.sign(key, message)geefthmac.new(key, message, hashlib.sha256).hexdigest()terug.keyenmessagezijnbytes, en het resultaat is 64 hextekens tekst.verify(key, message, tag)berekent de tag vanmessageonderkeyopnieuw en geeftTrueterug als die gelijk is aantag, en andersFalse. Vergelijk voorlopig met==.
Bereik de standaardbibliotheek via haar
Voer python signing.py en python pl_sec.py forged-message uit in de
Klik daarna op Check my work. Die roept new_key() aan met een vervanger op de plaats van secrets.token_bytes die de grootte vastlegt waar je om vraagt, zodat hij kan zien dat de sleutel het resultaat is van één aanroep van secrets.token_bytes(32), en hij controleert dat twee echte sleutels verschillen. Hij vergelijkt sign() met verify() aan met de juiste sleutel, het juiste bericht en de juiste tag, en met een veranderd bericht, een veranderde tag, een andere sleutel, een ingekorte tag, een lege tag, een verlengde tag en de gewone True geven en de andere precies False. Hoe verify() vergelijkt, wordt in deze les niet gecontroleerd. Niets uit je terminal wordt beoordeeld.