Skip to content

Faran med att implementera kryptering själv

Det finns ett välkänt uttryck inom kryptografi som lyder: “don’t roll your own crypto”. Uttrycket handlar inte nödvändigtvis om att man inte ska skriva egna krypteringsalgoritmer (även om det oftast är en dum idé) utan om att man inte ska försöka sig på att implementera krypteringsscheman själv. För att illustrera riskerna med detta kommer jag i detta blogginlägg ge exempel på en enkel attack på en fortfarande vanlig implementation av AES.


Föreställ dig att du har fått ett krav på din nya mjukvara där all kommunikation ska krypteras med AES. Du tänker: “Hur svårt kan det vara? Jag laddar ned ett officiellt krypteringsbibliotek och använder AES-implementationen där”. Vad är problemet med detta – man använder ju ett bibliotek med vältestade implementationer?

Nedan kommer jag gå igenom hur AES-CBC fungerar och varför det är lätt att göra fel när det kommer till att implementera kryptering själv.

 

Kort om AES

AES står för Advanced Encryption Standard och har de senaste tjugo åren varit industristandard för krypteringsalgoritmer. Det är den enda algoritmen som är godkänd av amerikanska NSA för “top secret information”. AES används även i majoriteten av all HTTPS-kommunikation.1

AES är ett blockchiffer, vilket innebär att algoritmen endast kan kryptera data i block som är av rätt längd. AES-128 använder 16-byte block, AES-256 använder 32-byte block (16 respektive 32 tecken). Vill man kryptera data som är kortare än en blocklängd får man padda blocket, vilket innebär att man lägger till text för att fylla ut blocket till rätt längd. Vill man däremot kryptera data som är längre än ett block måste man ha ett sätt att “kedja ihop” block och kryptera varje block för sig.

För blockchiffer finns det flera olika operationslägen (engelska: mode of operation) för att möjliggöra kryptering av flera block. Varje läge kommer med olika egenskaper för att bevara konfidentialitet och integritet. I detta blogginlägg kommer vi kolla specifikt på Cipher Block Chaining (CBC).

 

CBC

Cipher block chaining (CBC) innebär att man “kedjar” ihop blocken med varandra. Föregående chiffertext, det vill säga krypterad klartext, kombineras (bitvis XOR) med klartexten i kommande block. Syftet med detta är att göra varje block med chiffertext unikt under samma nyckel. För att även det första blocket ska vara unikt tillsätter man ett godtyckligt valt block, helst slumpmässigt, som kombineras med det första blocket klartext. Det kallas initialiseringsvektor (IV).

 

krypto_bloggDekryptering i CBC

 

En välkänd attack på CBC-chiffer är “bitflip attack”, vilket är vad jag ska visa i detta blogginlägg.

 

Bitflip

Teorin bakom en bitflip attack är att ändra en eller flera bits i chiffertexten, vilket sedan kommer bevaras i klartexten vid dekryptering. Med en lyckad bitflip kan man ändra en chiffertext till att representera en godtycklig klartext utan att känna till krypteringsnyckeln. Enklare sagt, en lyckad attack kan få oss att ändra delar av krypterad data till valfri klartext, utan att känna till nyckeln.

 

I exemplet nedan vill vi ändra den sista bokstaven, Y, i vår klartext ”SECURITY”, till ett X när den dekrypteras.

krypto_blogg2

Som tidigare nämnt så fungerar CBC genom att föregående block med chiffertext kombineras med nuvarande block klartext. Det betyder enkelt sagt att:
Y = decrypt(H)^A.

Det är viktigt att notera att decrypt(H) != Y.

Att använda decrypt(H) är en förenkling, då det egentligen är det sista tecknet i blocket decrypt(EFGH) som menas, men detta är lättare att visualisera. All kryptering/dekryptering med AES sker blockvis.

 

Eftersom XOR är en associativt kan vi enkelt räkna ut vilket värde vi ska ersätta chiffertexten med genom följande ekvation:


blogg_krypto3

Vår attack innebär alltså att vi ändrar chiffertexten ABCDEFGH till: ABC(X^Y^D)|EFGH
vilket i sin tur dekrypteras till <&%/|RITX

Det första blocket med klartext blir till synes skräptecken eftersom vi ändrar ABCD till en annan chiffertext. Detta är till följd av “Avalance effect” vilket är en egenskap i de flesta blockchiffer och kryptografiska hashfunktioner som innebär att en liten förändring i input ändrar output markant.2

I detta exempel har vi, genom att justera chiffertexten till ett uträknat värde, fått klartexten att representera precis det tecken vi vill, utan att vi känner till krypteringsnyckeln.

 

Riktigt exempel

Om din applikation använder AES-CBC för att kryptera cookies hade en angripare kunnat ändra innehållet i en cookie till godtyckligt innehåll. I detta exempel kommer vi ändra rollen som lagras i cookien från “common” till “admin” genom en bitflip attack.


Notera här att det är implementationen från det officiella krypteringsbiblioteket för Python3 som används.

 
#!/usr/bin/python3

import json
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
 
 
class AES_CBC():
   
    def __init__(self, blksize=16):
        if blksize not in [16, 24, 32]:
            raise ValueError("Blocksize must be 16, 24 or 32")
 
        self.blksize = blksize
        self.key = get_random_bytes(self.blksize)
 
    def encrypt(self, plaintext):
        cipher = AES.new(self.key, AES.MODE_CBC)
        pt_padded = pad(plaintext, AES.block_size)
        ct_bytes = cipher.encrypt(pt_padded)
        iv = b64encode(cipher.iv).decode('utf-8')
        ct = b64encode(ct_bytes).decode('utf-8')
       
        return json.dumps({
            'IV':iv,
            'ciphertext':ct
        })
 
    def decrypt(self, cipher_obj):
        b64 = json.loads(cipher_obj)
        iv = b64decode(b64['IV'])
        ct = b64decode(b64['ciphertext'])
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        pt_padded = cipher.decrypt(ct)
        
        return unpad(pt_padded, AES.block_size)
   
 
if __name__ == "__main__":
    aes = AES_CBC()
    res = aes.encrypt(b"{id=11;date=20200129;role=common}”)
    #notera att role=common
   
    b64 = json.loads(res)
    ct = b64decode(b64['ciphertext'])
    iv = b64['IV']
   
    to_flip = list(ct)
    target = "admin;"
    before = "common"
    for i in range(6):
        to_flip[10+i] = ct[10+i]^ord(before[i])^ord(target[i])
   
    flipped = json.dumps({
        'IV':iv,
        'ciphertext': b64encode(bytes(to_flip)).decode('utf-8')
    })
 
    print(aes.decrypt(flipped))




> b'\x904\x1dg\xdbV\xcb\xe5\xf3\xd8E\xac\xc3\x99P\x080129;role=admin;}'


 



Tyvärr är CBC fortfarande ett relativt vanligt operationsläge, men bör aldrig användas utan integritetscheckar. En ännu bättre lösning är att använda autentiserad kryptering, som exempelvis GCM.3

 

Vad gör vi då åt saken?

Syftet med detta blogginlägg är att visa faran med att implementera krypteringsscheman själv, utan att ha stor insikt i de risker och attacker som finns. Trots att vi använde en godkänd algoritm för NSA:s högsta säkerhetsklassning och en vanlig implementation öppnade det upp för en enkel attack med katastrofala konsekvenser.

En bra regel är att implementera och göra så lite själv som möjligt, samt att uppdatera sig regelbundet kring vilka krypteringsalgoritmer och bibliotek man bör använda.

Det är viktigt att komma ihåg att kryptering endast bör vara en skyddsmekanism av flera. 

 

[1] https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
[2] https://en.wikipedia.org/wiki/Avalanche_effect
[3] https://en.wikipedia.org/wiki/Galois/Counter_Mode