0%

Hashes, MAC's en willekeurige waarden · eindproject

Eindproject: Project: een ondertekeningsmodule

Dit project maakt signing.py af en geeft de de tests die haar de rest van de cursus bewaken. Hoofdstuk 5 bouwt tokens op deze module, dus ze moet kloppen, en ze moet blijven kloppen als iemand haar later bewerkt.

De eisen:

  • new_key(), sign() en verify() gedragen zich zoals in les 2.

  • verify() neemt zijn beslissing via hmac.compare_digest, op bytes.

  • verify() geeft False terug, en geeft geen fout, voor een verkeerde tag die bestaat uit willekeurige tekst die een header kan dragen: een korte, een lege, een met tekens buiten ASCII.

  • tests/test_attacks.py bevat vier nieuwe aanvalstests met vaste namen.

Vier aanvallen, vier tests

Elke test speelt één aanvaller en controleert dat signing.verify(...) False teruggeeft, of in het laatste geval dat hij op de juiste manier vergeleek.

De vervalser heeft geen sleutel. Hij verandert het bericht en heeft er een tag voor nodig. Het beste wat hij kan berekenen, is hashlib.sha256(message).hexdigest(), de gewone digest van zijn nieuwe bericht. test_attack_forged_tag_is_refused geeft precies dat aan verify.

De inkorter heeft een juiste tag gezien en stuurt alleen de eerste tekens ervan, in de hoop dat de controle niet verder kijkt. test_attack_truncated_tag_is_refused ondertekent een bericht en kort de tag in met een , zoals tag[:8].

De buitenstaander heeft een eigen sleutel, op dezelfde manier gemaakt als de jouwe, en ondertekent daarmee. test_attack_tag_from_another_key_is_refused maakt twee sleutels, ondertekent met de ene en verifieert met de andere. Het bericht blijft hetzelfde, dus de sleutel is het enige wat verschilt.

De vierde aanvaller is die uit les 3, die leert van hoe de vergelijking werkt. Die aanval kan hier niet worden uitgevoerd, omdat op deze site niets getimed wordt. Daarom controleert de vierde test het ding dat hem tegenhoudt. helpers.count_compare_digest() geeft een terug waarvan de vermelding "calls" vanaf dat moment elke aanroep van hmac.compare_digest telt, terwijl de echte nog steeds het vergelijken doet. Onderteken eerst een bericht, start daarna de teller, roep dan verify aan met de juiste tag, en verwacht True en precies één aanroep. Start de teller als laatste, vlak voor de aanroep die je wilt tellen. In latere hoofdstukken doet de voorbereiding zelf ook vergelijkingen.

Die vierde test is van een andere soort dan de andere drie. Die testen wat een buitenstaander zou kunnen waarnemen. Deze kijkt naar binnen en controleert welke functie het vergelijken deed. Het is een test van hoe de code geschreven is, niet van wat hij doorlaat, en het is de enige manier om deze eigenschap zonder klok te testen.

Mijlpaal 1: schrijf de tests

Schrijf de vier tests onder het commentaar voor deze les in tests/test_attacks.py. Bereik de module als signing.new_key(), signing.sign(...) en signing.verify(...). Voer ze daarna uit:

python -m pytest -q

De run eindigt met 2 failed, 3 passed. Drie van je nieuwe tests slagen meteen tegen de code uit les 2, die die vervalsingen al weigert. Twee tests falen: die uit hoofdstuk 1, die boven aan het bestand als verwacht wordt genoemd, en je vergelijkingstest, met assert 0 == 1. Nul aanroepen: je verify gebruikt nog ==. Als je verify al hmac.compare_digest aanroept, slaagt die test ook.

Om één test uit te voeren, selecteer je hem met een woord uit zijn naam. De optie -k houdt alleen de tests waarvan de naam dat woord bevat:

python -m pytest -q -k forged

Mijlpaal 2: laat elke test één keer falen

Drie tests slaagden de eerste keer dat je ze uitvoerde. Dat is prettig, en het bewijst weinig. Een test die nooit gefaald heeft, kan misschien niet falen.

Breek de code dus met opzet en kijk. Verander in signing.py sign even zo dat hij hashlib.sha256(message).hexdigest() teruggeeft en de sleutel weglaat. Voer python -m pytest -q -k forged uit. De test voor de vervalste tag moet nu falen, omdat de gewone digest van een vervalser geverifieerd wordt. Zet de regel terug en zie de test weer slagen. Doe hetzelfde voor de andere twee: een verify die alleen expected[:len(tag)] met de tag vergelijkt, moet de test voor de ingekorte tag laten falen, en een sign die altijd één vaste sleutel gebruikt, zoals b"k" * 32, moet de test voor de andere sleutel laten falen.

Check my work doet hetzelfde met vier kapotte versies van de module, één voor elke test, en verwacht dat elke test faalt tegen zijn eigen versie.

Je test voor de ingekorte tag slaagt. Daarna verander je verify zo dat hij elke tag accepteert die een prefix van de juiste is, en de test slaagt nog steeds. Wat heb je geleerd?

Mijlpaal 3: de vergelijking

Laat nu de laatste test slagen. Bewaar in verify de opnieuw berekende tag in een , codeer beide tags met .encode("utf-8"), en geef terug wat hmac.compare_digest(...) voor de twee bytestrings teruggeeft.

Voer alle tests opnieuw uit met python -m pytest -q. De run eindigt met 1 failed, 4 passed, en de enige mislukking is de test uit hoofdstuk 1. Voer daarna het hulpprogramma uit:

python pl_sec.py forged-message

De laatste regels luiden nu:

--- how verify() compared ---
hmac.compare_digest was called 5 times in 5 checks
--- result ---
5 of 5 forgeries refused

Waarom gebruikt de vervalser in de test voor de vervalste tag hashlib.sha256(message).hexdigest() als tag?

Wat nog niet verandert

SECURITY.md blijft zoals hij is. F2 gaat over de login, en de login in reading_server.py vergelijkt een wachtwoord nog steeds met !=. Dit hoofdstuk bouwde het gereedschap. Hoofdstuk 3 gebruikt het om de rij te sluiten.

Klik op Check my work als alle vier de tests slagen. Je hebt nu twee dingen waar elk later hoofdstuk op steunt: een tag die alleen de sleutelhouder kan maken, en een vergelijking die niet laat zien waar twee verschillen. Hoofdstuk 3 gaat over het oudste geheim in de dienst, het wachtwoord.

Opdracht

Maak signing.py af en schrijf de vier aanvalstests ervoor.

Schrijf in tests/test_attacks.py, onder het commentaar voor deze les:

  • test_attack_forged_tag_is_refused: maak een sleutel met signing.new_key(). De vervalser verandert het bericht en gebruikt hashlib.sha256(message).hexdigest() als tag. Controleer dat signing.verify(...) False teruggeeft.

  • test_attack_truncated_tag_is_refused: onderteken een bericht, kort de tag in met een , en controleer op False.

  • test_attack_tag_from_another_key_is_refused: maak twee sleutels, onderteken een bericht met de ene, verifieer hetzelfde bericht met de andere, en controleer op False.

  • test_attack_tag_is_compared_with_compare_digest: onderteken een bericht, start daarna counter = helpers.count_compare_digest(), roep signing.verify(...) aan met de juiste tag, en controleer dat die True teruggaf en dat counter["calls"] 1 is.

Verander daarna verify in signing.py zo dat hij beide tags codeert met .encode("utf-8") en teruggeeft wat hmac.compare_digest(...) teruggeeft.

Voer python -m pytest -q uit in de : hij moet eindigen met 1 failed, 4 passed, waarbij de enige mislukking de test uit hoofdstuk 1 is.

Klik daarna op Check my work. Die controleert new_key(), sign() en verify() zoals in les 2, met andere . Hij roept verify() aan met een vervanger op de plaats van hmac.compare_digest die altijd hetzelfde antwoord geeft, om te zien dat je teruggeeft wat de vergelijking teruggaf, en hij stuurt een tag van 64 niet-ASCII-letters, die False moet geven. Daarna voert hij je vier tests op naam uit, met signing.new_key, signing.sign en signing.verify vervangen door andere versies: tegen een correcte moeten alle vier slagen, en tegen vier kapotte modules (een sign zonder de sleutel, een verify die een prefix accepteert, een sign die zijn sleutel negeert, een verify die == gebruikt) moet de bijbehorende test falen. De test uit hoofdstuk 1 wordt niet uitgevoerd, en niets uit je terminal wordt beoordeeld.