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 | Já |
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
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
þ.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.
- Hversu mörg bæti eru í lyklinum?
- Hversu margir 16 bæta bálkar eru í gögnunum, áður en hugsanlegri fyllingu er bætt við?
- Fáum við annað úttak ef við sendum sama bálk tvisvar í AES með sama lykli?
Lausn við æfingu 1
- Lykillinn er \(256 / 8 = 32\) bæti.
- Gögnin eru \(48 / 16 = 3\) bálkar.
- 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).
Dulráðningin fylgir þá sama mynstri, við skiptum dulritinu upp í 16 bæta bálka og dulráðum hvern fyrir sig með sama lykli.
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
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.
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
- Hvaða stöður innihalda eins frumtextabálka?
- Getur Eva sagt út frá þessu einu hvað fyrsti frumtextabálkurinn inniheldur?
- Hvað bætist við vitneskjuna ef Eva veit að fyrsti frumtextabálkurinn var
b"admin=0;uid=042;"?
Lausn við æfingu 2
- Stöður 1, 3 og 4 eru eins; stöður 2 og 6 eru líka eins.
- Nei. Hún sér endurtekningar en þekkir ekki sjálfkrafa innihaldið.
- Þá 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
- Hver er PKCS#7-fyllingin fyrir 30 bæta skilaboð með AES-ECB?
- Hversu löng verður bætarunan eftir fyllingu ef skilaboðin eru 32 bæti?
- 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
- Það vantar tvö bæti í næsta 16 bæta margfeldi, svo við bætum við
02 02. - Hún verður 48 bæti. Við bætum heilum 16 bæta fyllingarbálki við 32 bætin.
- Við fjarlægjum síðustu tvö bætin,
02 02, en höldum41 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.
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.
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ð
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\).
Ef við skilgreinum \(IV\) sem \(C_0\), má setja fram almenna reglu um dulritun og dulráðningu bálks \(i\) þannig að
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.
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.
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.
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.
- Hvað fáum við með því að XOR-a dulritin saman?
- Ef við þekkjum fyrstu 8 bæti fyrri skilaboðanna, hvað getum við fundið um hin?
- Höfum við þar með fundið AES-lykilinn?
Lausn við æfingu 5
- XOR af frumtextunum, yfir þann hluta sem notar sama straum.
- Fyrstu 8 bæti seinni skilaboðanna, með \(C_1 \oplus C_2 \oplus P_1\) á þessum stöðum.
- 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.
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ð
mun Bjarni dulráða breytta dulritið og fá
Þ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.
# 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.
- Hvaða gildi XOR-um við við samsvarandi dulritsbæti?
- Þurfum við að finna lykilinn fyrst?
Lausn við æfingu 6
- Við reiknum
0x30 ^ 0x31, sem er0x01. Við snúum því við lægsta bitanum. - 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.
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:
read_textles hex-textann;bytes.fromhexbreytir hverjum tveimur hex-tölustöfum í eitt bæti. Lykillinn er því 32 bæti, ekki 64 textabæti.data[:16]tekur IV ogdata[16:]tekur afganginn. Við notum ekki IV sem hluta dulritsins.updatevinnur með gögnin ogfinalizelýkur aðgerðinni. Við tökum með úttak beggja kalla.- CBC-forritaskilin fjarlægja ekki fyllinguna sjálf.
PKCS7(128)notar bálkastærð í bitum: ekki skrifaPKCS7(16)hér. - 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