Hoppa yfir í efnið

Samhverf dulritun

Í síðasta kafla notuðum við XOR og skoðuðum einskiptislykla. Nú færum við okkur nær dulritunarkerfum sem eru notuð í dag. Við höldum áfram með Önnu og Bjarna: þau vilja senda leynileg skilaboð, en Eva getur bæði lesið dulrituðu skilaboðin og reynt að breyta þeim.

Sú dulritun sem við höfum séð hingað til kallast samhverf dulritun (e. symmetric encryption), en með henni notum við sama lykilinn til að dulrita og dulráða gögn. Þegar gögn eru send þurfa Anna og Bjarni því bæði að hafa lykilinn, en mikilvægt er að Eva fái hann ekki því þá getur hún lesið upprunalegu skilaboðin.

flowchart TD
    P["Skilaboð Önnu"] --> E["Dulritun"]
    K["Sameiginlegur<br/>leynilykill"] --> E
    E --> C["Dulrit<br/>Eva getur hlerað"]
    C --> D["Dulráðning"]
    K --> D
    D --> R["Bjarni fær<br/>skilaboðin aftur"]

Samhverf dulritun er yfirleitt mjög skilvirk og hentar því vel fyrir stórar skrár, myndbönd og löng skilaboð. Í þessum kafla gerum við ráð fyrir að Anna og Bjarni hafi þegar komið sér saman um lykil. Hvernig hægt er að framkvæma það yfir ótraust net skoðum við í kaflanum um lykildreifingu.

Frá einskiptislykli að endurnýtanlegum lykli

Eins og við ræddum í síðasta kafla, þá er möguleiki að framkvæma örugga dulritun með einskiptislyklum, en þeim fylgja þó ýmsir ókostir. Einn af þeim er stærð. Einskiptislykill þarf að vera jafn langur og skilaboðin sem send eru. Til að senda gígabæti af gögnum þurftum við því að deila gígabæta lykli fyrir fram. Það er sjaldnast hentugt.

Samhverf nútímadulritun notar í staðinn stuttan lykil, til dæmis 32 bæti, jafnvel þótt unnið sé með miklu lengri gögn. Hægt er að notað sama lykil fyrir mörg skilaboð, svo lengi sem við fylgjum reglum dulritunarinnar þegar kemur að upphafsgildi, hámarksmagni gagna, o.s.frv.

Þessar aðferðir eru þó ekki jafngildar einskiptislyklum. Við treystum því að árás án lykilsins krefjist svo mikils tíma og reikniafls að óraunhæft sé að framkvæma hana. Við athugum að það telst ekki jafn öruggt og einskiptislykill, sem heldur gögnum leyndum jafnvel þótt árásaraðili hafi ótakmarkað reikniafl.

Hvað þarf gott kerfi að geta?

Þegar Anna sendir Bjanra skilaboð viljum við tryggja bæði leynd (e. confidentiality), svo Eva geti ekki lesið skilaboðin, og heilleika gagna (e. data integrity), svo breytingar hennar uppgötvist.

Hluti Hlutverk Má Eva sjá hann?
Dulritunaralgrím Reglurnar sem umbreyta gögnunum Já; öryggið á ekki að byggjast á leynd algrímsins
Lykill Stjórnar útreikningunum Nei
Frumstillivigur (e. initialization vector, IV) eða eintölu Upphafsgildi sem aðferðin notar með lyklinum Já, í aðferðunum sem við skoðum hér
Sannvottunarkóði (e. message authentication code) Gerir viðtakanda kleift að sannreyna gögnin með lyklinum

Frumstillivigur og eintöla gegna skyldum hlutverkum, en reglurnar um þau eru ekki alltaf þær sömu. Við skoðum þær með hverri aðferð. Sannvottunarkóðinn bætist við þegar við viljum líka greina breytingar, en það er ekki sjálfkrafa hluti af allri dulritun.

Bálkadulritun og straumdulritun

Nútímadulritun má skipta í tvo flokka, annars vegar bálkadulritun (e. block cipher) og hins vegar straumdulritun (e. stream cipher).

Bálkadulritun

Bálkur (e. block) er hluti gagna, af fastri lengd, sem farið er með sem eina heild. Bálkadulritun tekur einn bálk og skilar jafn löngum dulrituðum bálki. Lykillinn ákvarðar umbreytinguna, og með sama lykli getum við líka snúið henni við.

Straumdulritun

Straumdulritun notar lykil til að frumstilla aðferð sem framkallar svo (óendanlega) runu bæta, sem kallast dulritunarstraumur (e. keystream). Dulritið er svo fengið með því að taka XOR af straumnum og gögnunum. Ef við notum \(P\) fyrir frumtextann, \(C\) fyrir dulritið og \(S\) fyrir strauminn, getum við þannig skilgreint dulritunina og dulráðninguna sem

\[ C = P \oplus S, \qquad P = C \oplus S. \]

Straumdulritun er þannig keimlík dulritun með einskiptislykli, en stærsti munurinn er að lykillinn er ekki allur valinn af handahófi heldur framleiddur út frá styttri lykli sem er valinn af handahófi.

AES

AES (e. Advanced Encryption Standard) er eitt þekktasta staðlaða samhverfa dulritunaralgrímið sem notað er í dag. Algrímið vinnur eingöngu með 128 bita bálka, eða 16 bæti, en hægt er að velja milli þriggja lykillengda.

Útgáfa Lykill í bitum Lykill í bætum Bálkur í bætum
AES-128 128 16 16
AES-192 192 24 16
AES-256 256 32 16

Við táknum dulritun eins bálks með \(E_K(P)\), þar sem \(K\) er lykillinn. Samsvarandi dulráðning er \(D_K(C)\). Þá gildir

\[ D_K(E_K(P)) = P, \]

þ.e., ef við dulritum bálk og dulráðum niðurstöðuna með sama lykli fáum upprunalega bálkinn aftur.

Eðli AES er að sami lykill og sami inntaksbálkur gefa alltaf sama AES-úttaksbálk. Það gerir það að verkum að vanda þarf til verka þegar kemur að lengri skilaboðum þannig að endurtekningar verði ekki sýnilegar.

AES er byggingareining

Mikilvægt er að hafa í huga að AES er ekki fullbúið dulritunarkerfi, heldur kerfi sem dulritar aðeins 16 bæta bálka. Til að smíða kerfi sem getur dulritað skilaboð af hvaða lengd sem er þurfum við að ákvarða reglur um hvernig gögnum er skipt niður í bálka og hvernig AES er beitt á þá og hvernig heildardulritið er smíðað. Sú aðferð kallast rekstrarhamur (e. mode of operation) dulritunarinnar, eða einfaldlega hamur.

Við ætlum ekki að útfæra AES sjálf hér. Við lítum frekar á AES sem trausta byggingareiningu, ein skonar „svartan kassa“, og einbeitum okkur að því hvernig hún er notuð og hvernig röng notkun getur valdið öryggisgöllum í dulritun.

Vissir þú?

AES varð staðall árið 2001 eftir alþjóðlega samkeppni og opinbera rannsókn margra tillagna. Vinningsalgrímið hét Rijndael og var hannað af Joan Daemen og Vincent Rijmen. Traustið byggðist ekki á því að fela aðferðina, heldur að leyfa sérfræðingum að rannsaka hana.

Gömul og úrelt algrím

DES varð staðall árið 1977, en 56 bita lykill þess er of stuttur til að verjast þrautleit með nútímatækjum. Straumdulritið RC4, frá 1987, reyndist hafa skaðleg mynstur í dulritunarstraumnum. Við notum hvorugt í nýjum kerfum. Það að aðferð hafi einu sinni verið vinsæl gerir hana ekki örugga að eilífu.

Æfing 1: lykill eða bálkur?

Við notum AES-256 til að vinna með 48 bæti af gögnum.

  1. Hversu mörg bæti eru í lyklinum?
  2. Hversu margir 16 bæta bálkar eru í gögnunum, áður en hugsanlegri fyllingu er bætt við?
  3. Fáum við annað úttak ef við sendum sama bálk tvisvar í AES með sama lykli?
Lausn við æfingu 1
  1. Lykillinn er \(256 / 8 = 32\) bæti.
  2. Gögnin eru \(48 / 16 = 3\) bálkar.
  3. Nei. AES sjálft gefur sömu niðurstöðu. Hamurinn í kringum AES þarf að koma í veg fyrir að þetta leki mynstrum úr skilaboðunum.

ECB

Það liggur beinast við, ef dulrita á löng skilaboð með AES, að skipta skilaboðunum niður í 16 bæta bálka og dulrita hvern bálk sjálfstætt með sama lykli og skeyta svo dulrituðu bálkunum saman. Sá hamur kallast ECB (e. Electronic Codebook).

Dulritun í ECB-ham: þrír frumtextabálkar fara hver í sína bálkadulritun með sama lykli og verða að þremur dulritsbálkum.
Dulritun í ECB-ham. Hver bálkur fer sjálfstætt í gegnum bálkadulritunina og allir bálkarnir nota sama lykilinn.

Dulráðningin fylgir þá sama mynstri, við skiptum dulritinu upp í 16 bæta bálka og dulráðum hvern fyrir sig með sama lykli.

Dulráðning í ECB-ham: þrír dulritsbálkar fara hver í sína bálkadulráðningu með sama lykli og verða að þremur frumtextabálkum.
Dulráðning í ECB-ham. Hver bálkur er dulráðinn óháð hinum.

ECB er einfalt í útfærslu, þar sem hægt er að dulrita hvern bálka óháð öllum hinum, en það er einmitt þetta sjálfstæði bálkanna sem veldur vandræðum. Segjum sem svo að við séum með þrjá frumtextabálka \(A, B, A\) (hér táknar hver bókstafur heilan 16 bæta bálk, ekki einn staf) og allir bálkarnir fara í gegnum AES-ECB með sama lykli \(K\).

Staða Frumtextabálkur Dulritsbálkur
1 \(A\) \(X\)
2 \(B\) \(Y\)
3 \(A\) \(X\)

Sömu gögnin \(A\) gefa þá sama dulritið \(X\). Ef Eva er að hlera, veit hún ekki endilega hvað \(A\) inniheldur, en hún sér að fyrsti og þriðji hlutinn frumtextans er eins. Ef hún kemst á snoðir um einn frumtextabálk getur hún dulráðið alla eins bálka sem voru dulritaðir með sama lykli.

Sýnidæmi: mynstur sem sést í gegnum dulritun

Þrír reitir: bókstafurinn H í frumgögnum, auðþekkjanlegt H úr endurteknum röndum eftir AES-ECB og suð án sýnilegs H eftir AES-CTR.
Frá vinstri: frumgögn, AES-ECB og AES-CTR. Sama mynd og sami AES-lykill eru notuð í báðum dulritunum. CTR skoðum við hér á eftir.

Myndin er búin til með því að dulrita óþjöppuð myndgögn, eitt bæti fyrir hvern díl, og túlka bæti dulritsins aftur sem díla. Jafnlitir fletir innihalda marga eins 16 bæta bálka. Í ECB verða þeir að endurteknum bálkum af nýjum birtugildum, svo lögunin sést enn.

Mörgæsin sem sýndi veikleikann

Fræg útgáfa af þessu dæmi notar mörgæsina Tux, sem er lukkudýr stýrikerfisins Linux. Dílar myndarinnar eru dulritaðir með AES-ECB, en þrátt fyrir að gögnin breytist þá sjáum við enn mynstur þeirra.

Til vinstri upprunalega Tux-mörgæsin. Til hægri sama mynd eftir að hrá dílagögnin voru dulrituð með AES-128-ECB: útlínur mörgæsarinnar, augun og fæturnir sjást enn þótt litirnir breytist.
Tux, upprunaleg og AES-128-ECB-dulrituð. Sama grunnhugmynd og reitirnir hér að ofan, en á raunverulegri mynd.

Litirnir í dulritinu segja ekkert um upprunalegu litina, þeir stjórnast af lyklinum. En jafnlitir fletir í frummyndinni, eins og bakgrunnurinn og kviðurinn, verða áfram að endurteknum bálkum og halda þannig lögun mörgæsarinnar. Þetta er nákvæmlega sami leki og við sáum hér að ofan. Skámynstrið í röndóttu svæðunum kemur til af því að breidd myndarinnar í bætum er ekki margfeldi af 16, svo bálkarnir raðast ekki nákvæmlega eftir línum.

Æfing 2: hvað veit Eva?

Eva sér aðeins eftirfarandi ECB-dulritsbálka, alla dulritaða með sama lykli.

X Y X X Z Y
  1. Hvaða stöður innihalda eins frumtextabálka?
  2. Getur Eva sagt út frá þessu einu hvað fyrsti frumtextabálkurinn inniheldur?
  3. Hvað bætist við vitneskjuna ef Eva veit að fyrsti frumtextabálkurinn var b"admin=0;uid=042;"?
Lausn við æfingu 2
  1. Stöður 1, 3 og 4 eru eins; stöður 2 og 6 eru líka eins.
  2. Nei. Hún sér endurtekningar en þekkir ekki sjálfkrafa innihaldið.
  3. Þá veit hún að bálkar 3 og 4 innihalda líka þessi sömu 16 bæti. Hún þarf ekki að finna AES-lykilinn til að nýta lekann.

Ekki nota ECB!

AES-ECB er ágæt leið til að kynnast því hvernig hægt er að nota bálkadulritun til að dulrita gögn af hvaða stærð sem er, en vegna þess galla sem við höfum farið yfir hér er alls ekki mælt með notkun þessarar aðferðar á raungögn.

Fylling

ECB vinnur á 16 bæta frumtextabálkum, sem sendir eru inn í AES, en hvað ef frumgögnin deilast ekki jafnt í 16 bæta bálka? Í því tilfelli notum við fyllingu (e. padding), sem eru viðbótarbæti lengja frumtextann þannig að hann stemmi við bálkastærðina. Eftir dulráðningu fjarlægir viðtakandinn fyllinguna og endar þannig með réttan frumtexta.

Algeng fyllingaraðferð er PKCS#7, en hún virkar þannig að ef það vantar \(n\) bæti til að lengd frumtexta stemmi við bálkastærð, bætum við við \(n\) bætum við frumtextann sem hafa öll tölugildið \(n\).

Ef við höfum frumtextann

Hello world!

sem er 12 bæti, og viljum fyllingu upp í 16 bæti, myndi PKCS#7 gefa okkur

Hello world!\x04\x04\x04\x04

þar sem 4 bæti vantar upp á að við náum 16 bætunum. Þá er vert að spyrja, hvað ef frumgögnin eru einmitt þessi 16 bæti, sem enda á fjórum \x04 bætum? Mun viðtakandinn ekki halda að þau séu hluti af fyllingunni og fjarlægja þau? Í rauninni væri ómögulegt fyrir viðtakandann að vita hvort þetta væru upprunalegu gögnin eða fylltu gögnina. Með PKDS#7 fyllingu er því fyllt upp í einn aukabálk ef gögnin stemma við bálkastærðina.

Þannig sér viðtakandi að ef gögnin eru

Hello world!\x04\x04\x04\x04

þá eru upprunalegu gögnin Hello world!, en ef viðtakandinn sér

Hello world!\x04\x04\x04\x04\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10

þ.e. gögn sem enda á 16 bætum með gildi 16 (\x10), þá eru upprunalegu gögnin

Hello world!\x04\x04\x04\x04

Æfing 4: reiknum fyllinguna

  1. Hver er PKCS#7-fyllingin fyrir 30 bæta skilaboð með AES-ECB?
  2. Hversu löng verður bætarunan eftir fyllingu ef skilaboðin eru 32 bæti?
  3. Síðustu bætin eftir dulráðningu eru 41 42 02 02. Hvaða bæti fjarlægjum við ef fyllingin er rétt?
Lausn við æfingu 4
  1. Það vantar tvö bæti í næsta 16 bæta margfeldi, svo við bætum við 02 02.
  2. Hún verður 48 bæti. Við bætum heilum 16 bæta fyllingarbálki við 32 bætin.
  3. Við fjarlægjum síðustu tvö bætin, 02 02, en höldum 41 42.

Python

Prófum að nota AES með Python. Við notumst við forritasafnið cryptography, en hægt er að setja það upp með

python -m pip install cryptography

en það er nú þegar búið að setja það upp á fylgiþjóninum.

Í þessu dæmi prófum við að dulrita frumtexta með ECB, þar sem fyrsti og þriðjði bálkurinn eru eins.

ecb_example.py
import secrets

from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

key = secrets.token_bytes(32)
message = b"A" * 16 + b"B" * 16 + b"A" * 16

# PKCS7 tekur bálkastærð í bitum: 128 bitar = 16 bæti.
padder = padding.PKCS7(128).padder()
padded_message = padder.update(message) + padder.finalize()

# ECB notar hvorki IV né eintölu.
aes = Cipher(algorithms.AES(key), modes.ECB())
encryptor = aes.encryptor()
ciphertext = encryptor.update(padded_message) + encryptor.finalize()

# Nýr vinnsluhlutur fyrir dulráðninguna.
decryptor = aes.decryptor()
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()

assert plaintext == message
assert len(ciphertext) == 64  # 48 bæti af gögnum og heill fyllingarbálkur.
print("Fyrsti og þriðji dulritsbálkur eins:", ciphertext[:16] == ciphertext[32:48])
print(plaintext.decode("ascii"))

CBC

CBC (e. Cipher Block Chaining) er bálkadulritunarhamur sem notar áhugaverða lausn til að forðast galla ECB. Við skiptum frumgögnunum upp í 16 bæta bálka, eftir sem áður, en þegar við dulritum ákveðinn bálk, skeytum við honum ekki við dulritið, eins og í ECB, heldur bætum einnig við XOR af dulritaða bálkanum á undan.

Þegar kemur að fyrsta bálkanum er þó enginn bálki á undan. Til að þessi aðferð gangi upp þurfum því að notast við svokallaðan frumstillivigur, sem er 16 bæta runa, valin af handahófi. Í fyrsta bálkanum er hún þá notuð í stað dulritaða bálkans á undan.

Dulritun í CBC-ham: hver frumtextabálkur er XOR-aður við fyrri dulritsbálk (eða IV fyrir fyrsta bálkinn) áður en hann fer í gegnum AES með sama lykli.
Dulritun í CBC-ham. Hringurinn með krossinum er XOR. Dulritsbálkur hvers bálks fer áfram í XOR aðgerð næsta bálks.

Ef við höfum tvo frumtextabálka \(P_1, P_2\) sem verða að dulritsbálkunum \(C_1, C_2\), með lyklinum \(K\) þá gildir að, jafnvel þótt \(P_1\) og \(P_2\) séu eins, fara þeir almennt ekki eins inn í AES. Öðrum er blandað við frumstillivigurinn \(IV\) með XOR, hinum við \(C_1\). Við höfum þannig rofið beina samsvörun endurtekinna frumtextabálka og dulritsbálka sem einkenndi ECB.

Dulráðning

Til að dulráða afturköllum við fyrst AES-skrefið, síðan reiknum við XOR við sama gildi og áður. Þannig dulráðum við fyrsta bálkinn með

\[ \begin{aligned} C_1 &= E_K(P_1 \oplus IV), \\ D_K(C_1) &= P_1 \oplus IV, \\ D_K(C_1) \oplus IV &= P_1. \end{aligned} \]

Seinna XOR-skrefið styttir út frumstillivigurinn \(IV\). Sambærileg aðferð gildir um næstu bálka, nema þar notum við dulritaða bálkinn á undan í stað \(IV\).

Dulráðning í CBC-ham: hver dulritsbálkur fer í gegnum AES og er svo XOR-aður við fyrri dulritsbálk (eða IV fyrir fyrsta bálkinn) til að fá frumtextann.
Dulráðning í CBC-ham. Dulritsbálkarnir fara bæði inn í AES-dulráðninguna og XOR við næsta bálk á eftir.

Ef við skilgreinum \(IV\) sem \(C_0\), má setja fram almenna reglu um dulritun og dulráðningu bálks \(i\) þannig að

\[ C_i = E_K(P_i \oplus C_{i-1}), \qquad P_i = D_K(C_i) \oplus C_{i-1}. \]

Frumstillivigur

Fyrir AES-CBC er frumstillivigurinn 16 bæti og má ekki vera endurnýttur né fyrirsjáanlegur þegar skilaboðin eru valin til dulritunar. Þessum eiginleikum náum við fram með því að framkalla 16 bæti með dulritunarslembitalnagjafa fyrir hver skilaboð.

Frumstillivigurinn þarf ekki að vera leyndur þar sem hann lekur engum upplýsingum um frumgögnin. Ólíkt lyklinum, má alls ekki endurnýta frumstillivigurinn og þarf því að velja nýjan frumstillivigur fyrir hver skilaboð. Honum er því skeytt fremst á dulritið, þ.e.

IV || C₁ || C₂ || C₃

(hér táknar || að bætarunum sé skeytt saman).

Python

Hér er dæmi um dulritun í Python með AES-CBC. Við veljum 16 bæta frumstillivigur með viðeigandi dulritunarslembitalnagjafa, þ.e. secrets forritasafninu.

cbc_example.py
import secrets

from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

key = secrets.token_bytes(32)
message = "Hittumst við bókasafnið klukkan 16.".encode("utf-8")
iv = secrets.token_bytes(16)  # Nýtt IV fyrir hverja dulritun.

padder = padding.PKCS7(128).padder()
padded_message = padder.update(message) + padder.finalize()
encryptor = Cipher(algorithms.AES(key), modes.CBC(iv)).encryptor()
ciphertext = encryptor.update(padded_message) + encryptor.finalize()
payload = iv + ciphertext  # Lykillinn fylgir ekki með.

# Viðtakandinn hefur þegar sama lykil og sendandinn.
received_iv = payload[:16]
received_ciphertext = payload[16:]
decryptor = Cipher(algorithms.AES(key), modes.CBC(received_iv)).decryptor()
padded_plaintext = decryptor.update(received_ciphertext) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()

assert plaintext == message
print("IV:", received_iv.hex())
print(plaintext.decode("utf-8"))
# Hittumst við bókasafnið klukkan 16.

CTR

CTR (e. Counter mode) er dæmi um straumdulritun. Það þýðir að við dulritum ekki frumtextabálkana sjálfan með AES, heldur notum við dulritunarlykilinn til að smíða nokkurs konar einskiptislykil, sem er kallaður straumur, og honum blandað við frumtextann með XOR. Til að framkalla strauminn skeytum við saman eintölu og teljara, þ.e. 0, 1, 2, 3, o.s.frv., í 16 bæta í inntaksbálka. Hver bálkur er svo dulritaður með AES, og niðurstaðan er dulritunarstraumurinn.

Dulritun í CTR-ham: eintala og teljari eru settir saman í einn bálk og dulritaðir með AES. Úttakið er XOR-að við frumtextann og myndar þannig dulritunarstrauminn.
Dulritun í CTR-ham. Teljarinn hækkar um einn milli bálka en eintalan helst óbreytt. AES-úttakið er aldrei sent áfram sjálft, aðeins notað sem dulritunarstraumur í XOR-inu.

Ef síðasti hluti skilaboðanna nær ekki fullum bálkki, notum við notum þann fjölda bæta sem þörf er á úr næsta straumbálki og sleppum afgangnum. CTR hamurinn þarf því enga fyllingu.

Við dulráðningu búum við til sama straum og var notaður við dulritið, og blöndum honum við dulritið til að stytta það út og fá aftur frumtextann. Þessi aðferð notar því eingöngu AES-dulritun, en ekki AES-dulráðningu.

Dulráðning í CTR-ham: eintala og teljari eru settir saman í einn bálk og dulritaðir með AES, nákvæmlega eins og við dulritun. Úttakið er XOR-að við dulritið til að fá frumtextann.
Dulráðning í CTR-ham. AES-kassinn dulritar enn, þrátt fyrir að við séum að dulráða. Sami straumur myndast því aftur og sama XOR skilar frumtextanum.

Teljarinn

Ýmsar leiðir eru í boði til að blanda saman eintölunni og teljurunum, en ein leið er að nota 8 bæta eintölu og 8 bæta teljara. Eintala aðgreinir skilaboðin; teljarinn aðgreinir bálkana innan þeirra. Til dæmis má skipta 16 bæta AES-inntaki í 8 bæta eintölu og 8 bæta teljara. Þetta er dæmi um uppsetningu, ekki algild stærðarregla fyrir öll bókasöfn.

                   16 bæti · 128 bitar · eitt AES-inntaksbálkur

┌─────────────── EINTALA ───────────────┓┏────────────── TELJARI ────────────────┐
│          8 bæti · fast gildi          ┃┃     8 bæti · +1 fyrir hvern bálk      │
├────┬────┬────┬────┬────┬────┬────┬────╋╋────┬────┬────┬────┬────┬────┬────┬────┤
│ 0  │ 1  │ 2  │ 3  │ 4  │ 5  │ 6  │ 7  ┃┃ 8  │ 9  │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │
└────┴────┴────┴────┴────┴────┴────┴────┻┻────┴────┴────┴────┴────┴────┴────┴────┘
└───────────────────┬───────────────────┘└───────────────────┬───────────────────┘
                 eintala                                 teljari

Sama eintala er endurtekin í hverjum bálki skilaboðanna en teljarinn hækkar um einn í hverjum bálki.

  bálkur 0  N N N N N N N N │ 00 00 00 00 00 00 00 00
  bálkur 1  N N N N N N N N │ 00 00 00 00 00 00 00 01
  bálkur 2  N N N N N N N N │ 00 00 00 00 00 00 00 02
                            ⋮
            └──────┬──────┘   └──────────┬──────────┘
                eintala               teljari

Er teljarinn nóg?

Við athugum að breytingarnar á milli bálkanna, sem notaðir eru til að framleiða strauminn, eru all jafnan smávægilegar, oft bara eitt bæti. Þó að þessi smávægilegur munur sé á bálkunum eru dulrituðu bálkarnir gjörólíkir. Þetta er einmitt eðli góðra dulritunaraðferða, eins og AES, að úttakið úr þeim á að segja sem minnst um inntakið til að minna hættuna á að upplýsingar leki. Því eru dulritunaralgrím smíðuð á þann hátt að smávægilegar breytingar í frumtextanum geta gefið gjörólíkar dulritaðar niðurstöður.

Skoðum eftirfarandi dæmi þar sem AES er notað til að framleiða fyrstu þrjá bálkana í dulritunarstraum, með gefnum lykli og eintölu.

Lykill K    2B 7E 15 16 28 AE D2 A6 AB F7 15 88 09 CF 4F 3C
Eintala N   F0 F1 F2 F3 F4 F5 F6 F7

Bálkur 0
  eintala || teljari     F0 F1 F2 F3 F4 F5 F6 F7 00 00 00 00 00 00 00 00
  straumur               0C 2F BB B6 5A D9 67 2A 19 FE FD 35 9B F3 4B 02

Bálkur 1
  eintala || teljari     F0 F1 F2 F3 F4 F5 F6 F7 00 00 00 00 00 00 00 01
  straumur               AF AF 9C F2 EA D9 6D 33 E0 A5 C1 D6 49 81 B0 CE
                         # 58/128 bitar ólíkir bálki 0 (45%)

Bálkur 2
  eintala || teljari     F0 F1 F2 F3 F4 F5 F6 F7 00 00 00 00 00 00 00 02
  straumur               23 20 A0 0C 94 0B 41 59 F6 63 C4 D6 8F 69 46 E2
                         # 68/128 bitar ólíkir bálki 0 (53%)

Um það bil helmingur bitanna breytist milli bálka (45% og 53% hér), sem er nákvæmlega það sem búast má við af góðu dulritunarreikniriti. Minnsta hugsanlega breyting á inntakinu (eitt bæti af sextán) veldur breytingu sem lítur út fyrir að vera algjörlega tilviljanakennd í úttakinu.

Eintalan

Eintala kallast á ensku nonce sem er stytting á number used once sem útleggst sem „tala notuð einu sinni“. Eins og nafnið gefu til kynna, er gríðarlega mikilvægt að nota aldrei sömu eintölu oftar en einu sinni með sama lykli. Ástæðan er sú að sama eintala og lykill framkalla nákvæmlega sama dulritunarstraum. Endurnýting slíks pars hefur því nákvæmlega sömu áhrif og endurnýting einskiptislykils.

Æfing 5: hvað hefur lekið?

Við vitum að tvö CTR-dulrit voru búin til með sama lykli, sömu eintölu og sama upphafsteljara.

  1. Hvað fáum við með því að XOR-a dulritin saman?
  2. Ef við þekkjum fyrstu 8 bæti fyrri skilaboðanna, hvað getum við fundið um hin?
  3. Höfum við þar með fundið AES-lykilinn?
Lausn við æfingu 5
  1. XOR af frumtextunum, yfir þann hluta sem notar sama straum.
  2. Fyrstu 8 bæti seinni skilaboðanna, með \(C_1 \oplus C_2 \oplus P_1\) á þessum stöðum.
  3. Nei. Við getum nýtt straumendurnotkunina án þess að finna lykilinn sem var notaður með AES.

Python

Hér sjáum við dæmi um dulritun með AES-CTR í Python. þegar CTR hamurinn er frumstilltur tekur hann allan upphafsbálkinn sem viðfang. Þess vegna skeytum við saman eintölunni og upphafstölunni 0, framsett með 8 bætum (í háendaframsetningu) og gefum mode.CTR það sem viðfang.

ctr_example.py
import secrets

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

key = secrets.token_bytes(32)
message = "Leynd skilaboð af hvaða lengd sem er.".encode("utf-8")
nonce = secrets.token_bytes(8)
initial_counter = 0
initial_block = nonce + initial_counter.to_bytes(8, "big")

# CTR þarf enga PKCS#7-fyllingu.
encryptor = Cipher(algorithms.AES(key), modes.CTR(initial_block)).encryptor()
ciphertext = encryptor.update(message) + encryptor.finalize()
payload = nonce + ciphertext  # Báðir aðilar vita að teljarinn byrjar í núlli.

# Endursmíðum sama upphafsbálk og byrjum strauminn frá byrjun.
received_nonce = payload[:8]
received_ciphertext = payload[8:]
received_initial_block = received_nonce + initial_counter.to_bytes(8, "big")
decryptor = Cipher(
    algorithms.AES(key), modes.CTR(received_initial_block)
).decryptor()
plaintext = decryptor.update(received_ciphertext) + decryptor.finalize()

assert len(ciphertext) == len(message)
assert plaintext == message
print(plaintext.decode("utf-8"))
# Leynd skilaboð af hvaða lengd sem er.

Leynd kemur ekki í veg fyrir breytingar

Hingað til höfum við einblínt á að verja leynd skilaboða. Ef við hugsum þetta út frá leikmönnunum okkar, þá höfum við verið að einbeita okkur að því að Anna og Bjarni geti átt leynileg samskipti, jafnvel þótt Eva geti komist yfir dulrituð skilaboð. En þetta er ekki það eina sem þarf að huga að. Hvað ef Eva breytir skilaboðum?

Ímyndum okkur að Anna sendi Bjarna skilaboð sem eru dulrituð með AES-CTR, en Eva nái milligöngu um skeytasendinguna og geti breytt skilaboðunum áður en þau fara til bjarna. Köllum dulritið \(C\), frumtextann \(P\) og dulritunarstrauminn \(S\). Látum \(\Delta\) vera bætarunu af sömu lengd og \(C\). Ef Eva áframsendir breytt dulrit sem fæst með

\[ C' = C \oplus \Delta \]

mun Bjarni dulráða breytta dulritið og fá

\[ \begin{aligned} P' &= C' \oplus S \\ &= (P \oplus S \oplus \Delta) \oplus S \\ &= P \oplus \Delta. \end{aligned} \]

Þannig hefur Evu tekist að breyta dulráðna textanum sem Bjarni endar með á fyrirsjáanlegan og skipulegan máta. Hún þarf hvorki að þekkja dulritunarstrauminn né AES-lykilinn til að framkvæma breytinguna.

Ef Eva hefur einhverja vitneskju um undirliggjandi gögnin sem verið er að dulrita, getur hún nýtt þær upplýsingar til að breyta dulráðnu gögnunum sem Bjarni sér.

Sýnidæmi

Segjum að skilaboðin séu upphaed=1 og Eva viti bæði sniðið og að síðasti stafurinn sé 1. Hún vill breyta honum í 9.

Stafur ASCII-bæti Tvíund
1 0x31 0011 0001
9 0x39 0011 1001
Mismunur með XOR 0x08 0000 1000

Við líkjum eftir CTR með því að taka XOR af stuttum straum og gögnunum, og sjáum þá hvernig breytingin ratar til Bjarna.

xor_breyting.py
# Anna og Bjarni þekkja strauminn.
plaintext = b"upphaed=1"
keystream = bytes.fromhex("a1 b2 c3 d4 e5 f6 07 18 29")
assert len(plaintext) == len(keystream)
ciphertext = bytes(p ^ s for p, s in zip(plaintext, keystream))

# Eva notar aðeins dulritið og vitneskju um síðasta stafinn.
modified_ciphertext = bytearray(ciphertext)
modified_ciphertext[-1] ^= ord("1") ^ ord("9")  # 0x08

# Bjarni dulræður
recovered_plaintext = bytes(c ^ s for c, s in zip(modified_ciphertext, keystream))
print(recovered_plaintext)
# b'upphaed=9'

Ef Eva veit ekki upphaflega stafinn getur hún samt snúið bitanum við, en veit þá ekki endilega hver niðurstaðan verður.

Æfing 6: spáum fyrir um breytinguna

Við vitum að eitt bæti í frumtexta er ASCII-stafurinn 0, með gildið 0x30. Við viljum breyta honum í 1, með gildið 0x31, í sams konar óauðkenndri XOR-dulritun.

  1. Hvaða gildi XOR-um við við samsvarandi dulritsbæti?
  2. Þurfum við að finna lykilinn fyrst?
Lausn við æfingu 6
  1. Við reiknum 0x30 ^ 0x31, sem er 0x01. Við snúum því við lægsta bitanum.
  2. Nei. XOR-breytingin flyst yfir í frumtextann við dulráðningu.

CBC

CBC leysir ekki heilleikavandann heldur. Munum að \(P_1 = D_K(C_1) \oplus IV\). Ef Eva skiptir \(IV\) út fyrir \(IV \oplus \Delta\), án þess að breyta \(C_1\), verður fyrsti sulráðni bálkurinn sem Bjarni sér \(P_1 \oplus \Delta\).

Framhaldsefni: þegar fylling verður að véfrétt

Í padding oracle árás nýtir árásaraðili þjón sem gefur aðgreinanleg viðbrögð eftir því hvort fylling breytts CBC-dulrits er rétt eða röng. Viðbragðið getur verið villutexti eða mælanlegur tímamunur.

Rétt PKCS#7-fylling segir aðeins að síðustu bætin passi við fyllingarregluna. Hún sannar ekki að skilaboðin séu óbreytt eða komi frá réttum sendanda. Jafnvel breytt dulrit getur fyrir tilviljun gefið rétta fyllingu.

Með mörgum markvissum breytingum getur árásaraðili notað já/nei-svörin til að endurheimta frumtexta bæti fyrir bæti, án AES-lykilsins. Þjónninn verður eins konar „véfrétt“ um falin gögn.

Við þurfum ekki að útfæra árásina hér. Mikilvæga reglan er að láta ekki óauðkennt dulrit berast áfram í vinnslu sem lekur slíkum upplýsingum. Tilbúin auðkennd dulritun er betri upphafspunktur en eigin CBC-samsetning; að breyta aðeins villutextanum útilokar ekki alla leka.

Auðkennd dulritun

Til að varðveita heilleika dulritaðra gagna er hægt að notast við auðkennda dulritun (e. authenticated encryption). Slíkar aðferðir búa bæði til dulrit og sannvottunarkóða sem er bundinn gögnunum með lyklinum. Ef eihver breyting á sér stað á dulrituðu gönunum eða sannvottunarkóðanum sjálfum, munn auðkenningarskref dulritunaraðferðinnar ekki stemma og ætti þá að hafna gögnunum.

Dæmi um slíkar dulritunaraðferðir eru, m.a.

  • AES-GCM (e. Galois/Counter Mode), sem notar AES með teljaraham og auðkenningu, og
  • ChaCha20-Poly1305, sem sameinar straumdulritið ChaCha20 og auðkenningaraðferðina Poly1305.

Báðar eru AEAD-aðferðir (e. Authenticated Encryption with Associated Data).

Sýnidæmi

Sækjum skrána cbc-message.hex og vistum hana. Skráin inniheldur gögn kóðuð með sextándakerfiskóðun. Eftirfarandi forsendur eru gefnar

Atriði Upplýsingar
Dulritun AES-256-CBC
Lykill (hex) 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Uppbygging dulrits 16 bæta frumstillivigur, dulrit í kjölfarið
Fylling PKCS#7 með 128 bita bálkum
Frumtexti UTF-8-kóðaður texti

Við dulráðningu skiptir röð aðgerða miklu máli, en

hex-texti → hrá bæti → aðskilja IV og dulrit
          → AES-CBC-dulráðning → fjarlægja PKCS#7 → lesa UTF-8

Notum cryptography forritasafnið í Python og vistum eftirfarandi sem read_cbc.py í sömu möppu og skrána.

read_cbc.py
from pathlib import Path

from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

key = bytes.fromhex(
    "000102030405060708090a0b0c0d0e0f"
    "101112131415161718191a1b1c1d1e1f"
)
data = bytes.fromhex(Path("cbc-message.hex").read_text(encoding="ascii"))
if len(data) < 32 or (len(data) - 16) % 16 != 0:
    raise ValueError("Vantar IV eða heila CBC-bálka")

iv = data[:16]
ciphertext = data[16:]
if len(key) != 32:
    raise ValueError("Þetta dæmi notar 32 bæta AES-256-lykil")

print("IV:", iv.hex())
print("Dulritsbæti:", len(ciphertext))

decryptor = Cipher(algorithms.AES(key), modes.CBC(iv)).decryptor()
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()

unpadder = padding.PKCS7(128).unpadder()
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()

Path("cbc-python.txt").write_bytes(plaintext)
print(plaintext.decode("utf-8"), end="")

Keyrum python read_cbc.py. Við fáum

IV: f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
Dulritsbæti: 32
gg{cbc_med_thekktum_lykli}

Hér gerast nokkur aðskilin skref:

  1. read_text les hex-textann; bytes.fromhex breytir hverjum tveimur hex-tölustöfum í eitt bæti. Lykillinn er því 32 bæti, ekki 64 textabæti.
  2. data[:16] tekur IV og data[16:] tekur afganginn. Við notum ekki IV sem hluta dulritsins.
  3. update vinnur með gögnin og finalize lýkur aðgerðinni. Við tökum með úttak beggja kalla.
  4. CBC-forritaskilin fjarlægja ekki fyllinguna sjálf. PKCS7(128) notar bálkastærð í bitum: ekki skrifa PKCS7(16) hér.
  5. Við varðveitum niðurstöðuna sem bæti og túlkun sem UTF-8 kemur síðast, því verkefnislýsingin sagði að frumtextinn væri texti.

Hrá bæti, hex og Base64

Ef verkefnið gefur hráa tvíundaskrá notum við Path("message.bin").read_bytes() í stað hex-afkóðunar. Ef það gefur Base64 getum við notað base64.b64decode(text.strip(), validate=True).

Bætastrengurinn b"f0" eru tvö ASCII-bæti, en bytes.fromhex("f0") er eitt bæti með gildið 0xf0. Gætum því vel að því að lyklar og önnur gögn séu rétt framsett þegar unnið er með dulritun.

Verkefni

Hér má finna öll verkefnin úr efni þessa kafla.

Opna verkefni