Kodeerimine ja krüpteerimine
Kodeerimine ja krüpteerimine on mõisted, mida võib vahel segi ajada. Siin peatükis uurimegi Pythoni abil mõlemat põhjalikumalt. Kõigepealt vaatame, kuidas andmeid ja faile arvutis kodeeritakse ning millised on erinevad kodeerimisstandardid ja -meetodid. Teises pooles tutvume krüptograafia, krüpteerimise ning räsimisega.
Ettevalmistus
Õpikust peaks olema läbitud 2. peatükk ning võiksid olla tuttav funktsioonide ja sõnedega. Samuti tuleb kasuks erinevate arvusüsteemide tundmine (kümnendsüsteem, kahendsüsteem, kuueteistkümnendsüsteem). Krüpteerimise jaoks tuleb paigaldada teegid cryptography
ja rsa
, mida saab käsurealt teha käsuga pip install cryptography rsa
. Kui jääd hätta, uuri õpikust moodulite paigaldamise juhiseid.
Bitid, baidid ja sümbolid
Arvutimälus salvestatakse kõike kahendsüsteemis ehk väikseim infokandja on bitt, millel on kaks võimalikku väärtust. Võib mõelda, et need väärtused on kas 1 või 0, sisse- või väljalülitatud, jah või ei või siis tõene või väär. Füüsiliselt võibki selleks olla midagi lülitisarnast, mis on kas sisse- või väljalülitatud.
Kui iga bitt suudab väljendada kahte võimalikku väärtust, siis kaks bitti koos suudavad väljendada kaks korda rohkem ehk 4 võimalikku väärtust. Kaheksat järjestikust bitti nimetatakse baidiks ja iga bait suudab seega hoida juba {$ 2*2*2*2*2*2*2*2 = 2^8 = 256 $} erinevat nullide ja ühtede kombinatsiooni ehk 256 erinevat väärtust. Ühte baiti salvestatud väärtusega võib niimoodi kirjeldada üht täisarvu vahemikus 0-255 ning mitme baidiga saab veel suuremaid arve väljendada.
Sümboltabelid
Kõik andmed kodeeritakse enne mällu kirjutamist arvudeks. Teksti puhul määrab sümboltabel, milline arv mingile tähemärgile vastab ning kodeerimisalgoritm otsustab, kuidas see arv mällu salvestada. Kõige lihtsam lahendus on leida iga tekstisümboli järjekorranumber sümboltabelis ning kodeerida see näiteks 8 biti abil. Sümboltabeleid ning vastavaid kodeeringuid on erinevaid.
ASCII
Esimene tabel, mis standardina populaarseks sai, oli ASCII (American Standard Code for Information Interchange). Selles on 128 sümbolit, sealhulgas peamised kirjavahemärgid, kõik ladina suured ja väiksed tähed, numbrid ja kontrollkoodid, nagu näiteks reavahetus, tabulatsioon ja teksti lõppu tähistav kood. ASCII-põhise teksti puhul piisab iga sümboli salvestamiseks ühest baidist, nii et kodeerimisalgoritm on väga lihtne.
Kahjuks sai selle tabeliga täielikult väljendada ainult inglise tekste. Kuna ühe baidiga saab väljendada rohkem kui 128 võimalikku väärtust, tekkisid mitmed ASCII variatsioonid, mis tähti juurde lisasid (või asendasid). Näiteks üks Lääne-Euroopa keelte tähestike kodeerimiseks mõeldud ja eesti keele ametlik ASCII variant on ISO 8859-15. Ühe ASCII tabeliga ei saa siiski mitut tähestikku ühel lehel korraga kasutada.
Unicode
Kuigi ASCII on ühel või teisel kujul jätkuvalt kasutusel, on tänapäeva tekstide kodeerimise standard Unicode, mis katab peaaegu kõiki maailma tähestikke. Esimesed 128 sümbolit Unicode'i nimekirjas on samad, mis ASCII standardis. Peamised Unicode kodeerimisalgoritmid või kodeeringud on UTF-8
, UTF-16
ja UTF-32
. Iga UTF (Unicode transformation format) tunneb kõiki Unicode'i sümboleid. Nende ainus vahe on selles, millisteks koodideks nad sümboleid kodeerivad:
UTF-8
on levinuim ning salvestab sümboleid 8, 16, 24 või 32 biti abil. Kõige väiksemate arvkoodidega sümbolid salvestatakse seega ühe baidi abil. Sellise kodeeringuga fail võtab kõige vähem mäluruumi, kui see sisaldab enamjaolt Unicode'i tähestiku esimesi (lühemate koodidega) sümboleid.UTF-16
salvestab kõiki sümboleid 16 või 32 bitiga ja on efektiivsem just siis, kui kasutatakse rohkem pikema koodiga sümboleid (nt. Aasia tähestikud).UTF-32
salvestab iga sümbolit alati 32 biti abil, mis võtab kõige rohkem ruumi, aga kodeerimine on lihtsam, sest iga sümbol on mälus sama pikkusega.
Unicode'i standardi järgi kodeerimine
Kodeerides tehakse klaviatuurilt või mujalt saadud sisend kõigepealt Unicode'i koodideks. Üks kood on siis vastava sümboli järjekorranumber Unicode'i tähestikus, tavaliselt kirjutatakse need kujul U+KOOD
. Võime neid koode vaadata kümnendsüsteemi arvudena, kuigi enamasti väljendatakse arvkoode kuueteistkümnendsüsteemis (heksakoodis), sest nii saab lühemalt.
Järgmisena kuvatakse vastav sümbol ekraanile, aga mällu võivad kodeeringud selle kirjutada erineval kujul. All tabelis on näide sellest, kuidas sõne Õhtust! 👋 (viimane sümbol peaks olema lehvitav käsi) Unicode'i arvkoodidena ja erinevates kodeeringutes kirja pannakse. Baidid on tabelis eraldatud tühikutega, v.a. UTF-32
reas:
Väljendusviis | Väärtused | ||||||||
---|---|---|---|---|---|---|---|---|---|
Tähemärgid ekraanil | Õ | h | t | u | s | t | ! | 👋 | |
Kümnendsüsteemis arvud | 213 | 104 | 116 | 117 | 115 | 116 | 33 | 32 | 128075 |
Heksakoodis arvud | d5 | 68 | 74 | 75 | 73 | 74 | 21 | 20 | 1f44b |
UTF-8 kodeering | c3 95 | 68 | 74 | 75 | 73 | 74 | 21 | 20 | f0 9f 91 8b |
UTF-16 kodeering | 00 d5 | 00 68 | 00 74 | 00 75 | 00 73 | 00 74 | 00 21 | 00 20 | d8 3d dc 4b |
UTF-32 kodeering | 000000d5 | 00000068 | 00000074 | 00000075 | 00000073 | 00000074 | 00000021 | 00000020 | 00 01 f4 4b |
Tavaline ASCII kodeering | - | 68 | 74 | 75 | 73 | 74 | 21 | 20 | - |
ISO 8859-15 kodeering | d5 | 68 | 74 | 75 | 73 | 74 | 21 | 20 | - |
On näha, et ASCII kodeeringutes seda sõne päris õigesti kirja panna ei saagi. Pole lihtsalt sellist ASCII varianti, kus on olemas käelehvitusmärk (ja samal ajal täht Õ). Käelehvitus on tekstis küll pigem harva kasutatav sümbol ja selle kood on üsna pikk. Tabelist võib näha, et igas UTF-kodeeringus tuleb selle salvestamiseks kasutada nelja baiti.
Paokoodid
Paojadad on spetsiaalsed koodid, millega saab sõnedesse sisestada sümboleid, mida sinna mingil põhjusel otse sisestada ei saa. Näiteks reavahetuse saab ilma Enter-klahvi vajutamata sisestada koodiga \n
, tabulatsiooni koodiga \t
. Unicode'i sümboleid, mida otse klaviatuurilt või kopeerides-kleepides sisestada ei saa, võib kirjutada kujul \UKOOD
, kus KOOD on vastav 8-kohaline Unicode'i sümboli heksakood. näiteks eelmises näites kasutatud sõne saab väljastada nii:
>>> print("Õhtust! \U0001F44B") Õhtust! 👋
Unicode'i sümboleid on nime või koodi järgi võimalik leida näiteks siit: https://graphemica.com/.
Pythonis on ka sisseehitatud moodul unicodedata
, millega saab üht-teist teha:
>>> import unicodedata as ud >>> ud.name("õ") 'LATIN SMALL LETTER O WITH TILDE' >>> ud.name("&") 'AMPERSAND' >>> ud.lookup("crescent moon") '🌙' >>> ud.lookup("ALCHEMICAL SYMBOL FOR STRATUM SUPER STRATUM-2") '🝝' >>>
Sõnede kodeerimine Pythonis
Pythonis saab teksti kuvada ka kodeeritud kujul. See on sisuliselt arvude jada, kus arvud asuvad ühe baidi kaupa järjest, nagu nad mälus asetsevad. Sellise baidijada saab luua nii:
>>> baidid = bytes("Õhtust! \U0001F44B", "utf-8") >>> baidid b'\xc3\x95htust! \xf0\x9f\x91\x8b'
Baidijada on vormistatud nagu sõne, aga selle ees on täht 'b' ning sümbolitena kuvatakse jutumärkide vahel ainult ASCII tähestiku sümbolid, ülejäänud näidatakse paokoodide abil. Kui see järjendina väljastada, saab näha iga baidi väärtuseid kümnendsüsteemis:
>>> list(baidid) [195, 149, 104, 116, 117, 115, 116, 33, 32, 240, 159, 145, 139]
Tegelikult saab huvi korral erinevate käskudega kätte ka kõik tabelis olnud esitused:
- Funktsioon
ord
tagastab sümboli järjekorranumbri Unicode tähestikus. Selle pöördfunktsioon onchr
. - Funktsioon
hex
teisendab kümnendsüsteemi arvu kuueteistkümnendsüsteemi arvuks. - Käsk
print(väärtus, end=" ")
väljastab väärtuse ekraanile nii, et lisab reavahetuse asemel lõppu tühiku. - Käsk
f"{arv:x}"
vormistab mingi arvu heksakoodis sõnena (f nagu format!).
>>> # Loob muutuja 's', mis sisaldab sõne >>> s = "Õhtust! \U0001f44b" >>> >>> # Kasutab iga sümboli peal funktsiooni 'ord', teeb neist järjendi: >>> [ord(c) for c in s] [213, 104, 116, 117, 115, 116, 33, 32, 128075] >>> >>> # Kasutab iga sümboli peal funktsiooni 'ord', seejärel 'hex', väljastab ekraanile kuueteistkümnendsüsteemi arvud: >>> for c in s: print(hex(ord(c)), end=" ") 0xd5 0x68 0x74 0x75 0x73 0x74 0x21 0x20 0x1f44b >>> >>> # Kasutab iga sümboli peal funktsiooni 'ord', väljastab iga arvu heksakoodis sõnena: >>> for c in s: print(f"{ord(c):x}", end=" ") d5 68 74 75 73 74 21 20 1f44b >>> >>> # Teeb sõnest 's' utf-8 kodeeringus baidijada, väljastab iga baidi heksakoodis: >>> for c in bytes(s, "utf-8"): print(f"{c:x}", end=" ") c3 95 68 74 75 73 74 21 20 f0 9f 91 8b >>> >>> # Teeb sõnest utf-16-be kodeeringus baidijada, kus 'be' tähistab baitide järjekorda, väljastab heksakoodis: >>> for c in bytes(s, "utf-16-be"): print(f"{c:02x}", end=" ")) 00 d5 00 68 00 74 00 75 00 73 00 74 00 21 00 20 d8 3d dc 4b >>> >>> # Lõpuks võime väljastada sõne ka utf-8 kodeeringus baitidena kahendsüsteemis: >>> for c in bytes(s, "utf-8"): print(f"{c:b}", end=" ") 11000011 10010101 1101000 1110100 1110101 1110011 1110100 100001 100000 11110000 10011111 10010001 10001011
Hea on teada ka, kuidas baite uuesti sõneks dekodeerida:
>>> str(baidid, "utf-8") 'Õhtust! 👋'
Funktsioonidele bytes
ja str
pidi andma argumendiks ka kodeeringu nime. Selle asemel võib kasutada ka meetodeid encode
ja decode
, mis kasutavad vaikimisi UTF-8
kodeeringut:
>>> s.encode() b'\xc3\x95htust! \xf0\x9f\x91\x8b' >>> baidid.decode() 'Õhtust! 👋'
Failikodeeringud
Lihtsamate tekstifailide (nt. laiendiga txt
) puhul on üldjuhul kogu fail kodeeritud ühe tekstikodeeringuga ja tavaliselt puudub isegi otsene info kasutatud kodeeringu kohta. Paljud programmid eeldavad, et faili sisu on kodeeritud operatsioonisüsteemi vaikekodeeringu või UTF-8
abil. Tavaliselt see eeldus kehtib, aga vahel peavad programmid erinevate tunnuste abil ja kavalustega kodeeringut tuvastama. Seegi ei õnnestu iga kord ja siis näeb kasutaja tähtede asemel kaste või muid kummalisi sümboleid. Tavaliselt saab ühel või teisel viisil programmile ka öelda, mis kodeeringuga ta faili lugema peaks. Pythonis tulebki tihti eesti täpitähti sisaldavat faili avades ise määrata selle kodeering parameetriga encoding: open("fail.txt", encoding="utf-8")
.
Faili laiend ja vorming
Järgmiste levinud laienditega failid on samuti tekstifailidena loetavad ning enamike puhul võib eeldada UTF-8
kodeeringut: py
, log
, html
, xml
, json
. Tasub siiski meeles pidada, et failivorming määrab faililaiendi, mitte vastupidi. Näiteks TXT-faili niisama ümber nimetamine PDF-failiks ei muuda failivormingut. Faililaiend annab pigem kasutajale ja programmidele vihje, millega tegu on. Samas võivad mõned programmid kasutada oma töös faililaiendeid, mis seostuvad enamikele kasutajatele millegi muuga, nt. pahavara TeslaCrypt 3.0 salvestab oma infot MP3-laiendiga failidesse.
Keerukamad failid
Mõned näited keerukamate vormingutega failidest on tekstidokumendid (nt. laiendiga docx
, odt
), samuti heli- ja videofailid ning käivitatavad EXE-failid. Nende põhisisuks ei ole tekst, vaid nad on kindla struktuuriga, mida vastavad programmid oskavad lugeda. Tihti koosneb fail kehast ja päisest. Päises on metaandmed, mis võivad kirjeldada faili struktuuri ja see aitab programmidel faili päris sisu ehk keha lugeda. Selliste failide kodeeringu kohta saab internetist lugeda ja nii kirjutada programme, mis neid faile õigesti loevad. Eriti heli- ja videofailide puhul kasutatakse vastava programmi või algoritmi jaoks tihti sõna koodek (ingl codec, sõnadest coder, decoder).
Failitöötlusvahendid Pythonis
Kõiki failitüüpe siiski käsitsi (de)kodeerima või reahaaval töötlema ei pea. Pythonis leidub mooduleid, mis kodeerimist ja erinevat liiki failide lugemist-kirjutamist lihtsamaks teevad. Mõned huvitavamad näited standardteegist:
Moodul | Kirjeldus |
---|---|
base64 | Baitide kodeerimiseks base64 kujule. Nii saab igasuguseid andmeid sisestada nt. URLi sisse või HTTP-päringusse. |
csv | CSV-failide lugemiseks ja kirjutamiseks. Failitüüpi CSV (Comma Separated Values) kasutatakse nt. tabelitöötlusprogrammides. |
configparser | Konfiguratsioonifailide (nt. ini ja config laiendiga) töötlemiseks. |
json | JSON-failide lugemiseks ja kirjutamiseks. See on laialt levinud ja kergesti loetav vorming, mida kasutatakse nt. andmebaasides ja veebis andmete hoidmiseks ning vahetamiseks. |
xml ja html | Moodulid märgistuskeeles failide töötlemiseks. Loe nende kohta lähemalt veebisisu parsimise peatükist. |
wave | WAV-helifailide baitide kujul lugemiseks ja kirjutamiseks. Helitöötluse kohta loe lähemalt vastavast peatükist. |
bz2 , lzma , zipfile ja shutil | Mõned moodulid failide ja andmete pakkimiseks ja tihendamiseks, ZIP-failide töötlemiseks. |
pickle | Pythoni objektide faili talletamiseks. Kasulik nt. masinõppemudelite või pikalt töötavate programmide seisu salvestamiseks, kui on vaja programm vahepeal sulgeda. |
sqlite3 | Andmebaasifailide töötlemiseks, selle kohta saad lugeda ka andmebaaside peatükist. |
Krüpteerimine
Vaatame krüpteerimist ka, sest siingi saab rääkida sümbolite või arvude teisendamisest. Mis on kahe termini vahe?
- Kodeerimise eesmärk on andmed mugavamasse vormingusse teisendada. Nt. arvuti jaoks arvudeks ja sama kodeeringu või koodeki abil inimeste jaoks tekstiks, pildiks või heliks.
- Krüpteerimise eesmärk on muuta andmed loetavaks ainult nendele inimestele, kellele need mõeldud on. Nt. e-kirja saajale.
Krüpteerimiseks kasutatakse lisaks algoritmile ka võtit, mis aitab pärast algset teavet taastada ehk dekrüpteerida. Võti ei ole parool, vaid dešifreerimiseks vajalik info, tavaliselt mingi kood. Eristatakse kahte liiki krüpteerimist: sümmeetriline ja asümmeetriline.
Sümmeetriline krüpteerimine
Ühe võtmega krüpteerimist nimetatakse sümmeetriliseks. See tähendab, et algoritm loob võtme ja krüpteerib sellega andmed mingiks üsna juhuslikuks sümbolite jadaks. Kasutatud võtme abil saab kasutaja need andmed sama (pöörd)algoritmiga dekrüpteerida. Sellised krüpteerimistehnikad on tänapäeval näiteks AES (Advanced Encryption Standard) ja Twofish. Sümmeetriline krüpteerimine sobib näiteks oma arvutis failide krüpteerimiseks.
Üks lihtsaim näide sellisest krüpteerimisest on Caesari salakiri või šiffer, mida olla kasutanud ka Julius Caesar. See toimib nii, et iga täht tekstis asendatakse tähega, mis on tähestikus mingi arv kohti ees või tagapool. Näiteks paremnihkega 3 nihutatakse kõik tähed kolm kohta edasi:
salasõna on õigekaval. -> ždodžüqd rq üljhndödo.
Võti ongi sellisel juhul arv 3 ning sellist šifrit on ilma arvutitagi üsna kerge murda. Pythonis saaks selle šifri realiseerida nii:
tähestik = "abcdefghijklmnopqrsšzžtuvõäöüxy" sõnum = "salasõna on õigekaval." krüpteeritud = "" for täht in sõnum: täheKoht = tähestik.find(täht) if täheKoht != -1: # Kui täht on tähestikus uueTäheKoht = täheKoht+3 krüpteeritud += tähestik[uueTäheKoht % len(tähestik)] else: # Kui pole tähestikus krüpteeritud += täht print(krüpteeritud)
Asümmeetriline krüpteerimine
Teise krüpteerimise liigi puhul kasutatakse kahte võtit: avalikku ja salajast. Avalik on teistega jagamiseks ja salajast peaks enda teada hoidma. Algoritmiga luuakse mõlemad võtmed ja kasutada saab neid paaril viisil:
- Andmed krüpteeritakse avaliku võtmega ja dekrüpteeritakse salajasega. Nii saab saata salajasi (krüpteeritud) sõnumeid, mida saab dekrüpteerida ainult isik, kellel on vastav salajane võti.
- Andmed krüpteeritakse salajase võtmega ja dekrüpteeritakse avalikuga. Nii saab iga avaliku võtme kasutaja sõnumit dekrüpteerides veenduda, et sõnumi krüpteeris nimelt vastava salajase võtme omanik. Salajase võtmega krüpteerimist nimetatakse allkirjastamiseks.
Mõlemat meetodit saab muidugi veel kombineerida, kui mõlemal osapoolel on üksteise avalik võti.
Levinuimad asümmeetrilist krüpteerimist kasutavad algoritmid on RSA (Rivest Shamir Adleman) ja ECDSA ( Elliptic Curve Digital Signature Algorithm). RSA loob näiteks võtmed kahe suure algarvu korrutamise teel. See on efektiivne, sest mitmesajakohaliste arvude tegurite leidmiseks kuluks praegustel arvutitel miljoneid aastaid. Mõlemat algoritmi kasutab muuhulgas Eesti ID-kaardi süsteem, kusjuures ID-kaardi salajane võti on selle kiibi sees ning sellega (de)krüpteerimine toimub ka seal. Väidetavalt pole veel keegi suutnud ID-kaardi kiipi ega selles asuvat võtit kopeerida.
Räsimine
Erinevalt krüpteerimisest on räsimine ühesuunaline tegevus ehk räsist enam algset sisendit taastada ei saa. Räsifunktsioon annab iga sisendi puhul fikseeritud pikkusega väljundi, kusjuures sama sisend annab alati täpselt sama väljundi. Kui sisendis kasvõi üks bitt ära muuta, on väljund täiesti erinev. Tuntud räsifunktsioonid on SHA-256 js MD5. Pythoni sisseehitatud moodulis hashlib
on need olemas, kasutamiseks tuleb neile anda "tooreteks baitideks" kodeeritud sisend:
from hashlib import sha256, md5 tekst = "See on algtekst." baidid = bytes(tekst, "utf-8") räsi = md5(baidid).hexdigest() print(räsi)
Funktsioon md5
loob räsi ja meetod hexdigest
tagastab selle sõne kujul heksakoodis. Soovi korral saab räsi kätte ka baitide kujul meetodiga digest
. Heksakoodis näeb antud teksti räsi välja selline:
cbc2dc85a49e94c28bfd118529834379
Räsi on üks viis, kuidas kontrollida, kas andmeid on kuidagi muudetud. Kui võtta näiteks algteksti lõpust punkt ära, saab sellest kohe hoopis teistsuguse räsi:
>>> print( md5(b"See on algtekst").hexdigest() ) 0fa978f48b7aa139e6660c06e5ed3217
Sool
Kasutajakontode andmebaasis üldjuhul paroole ei hoitagi, hoitakse nende räsisid. See on turvalisem, kuna räsist on peaaegu võimatu algandmeid taastada. Sisselogimisel kontrollitakse vaid, kas sisestatud parool annab sama räsi, mis on andmebaasis. Sellega kaasneb siiski üks turvarisk. Kui kahel isikul on samasugune parool ja kellelgi on andmebaasile ligipääs, siis saab näha kõiki isikuid, kellel on sama parooliräsi ja järelikult ka sama parool. Selle olukorra lahendamiseks lisatakse enne räsimist paroolile mingi lisajupp ehk sool - tavaliselt mingi unikaalne väärtus, mis samuti andmebaasi salvestatakse.
Plokiahel
Üks tehnoloogia, mis räsimisele toetub, on plokiahel. Nagu nimi ütleb, on see mingite andmeplokkide ahel, kusjuures plokid on ühendatud nii, et iga plokk sisaldab eelmise ploki räsi. Räsi toimib kui ploki identifikaator.
plokk = (andmed, eelmise_ploki_räsi) ploki_räsi = räsifunktsioon(plokk)
Selline ehitus tagab selle, et kui keegi on ahelas olemasoleva ploki sisu muutnud, siis ploki räsi uuesti arvutades selgub, et see ei klapi enam järgmises plokis hoitud räsiga. Teoreetiliselt võiks küll peale ploki muutmist luua ka igale järgnevale plokile uus räsi, aga see tegevus on tavaliselt raskendatud mingi lisamehhanismiga. Näiteks Bitcoini plokiahelas, kus iga plokk kirjeldab ühte rahatehingut, on ploki räsile rakendatud lisatingimus: see peab algama teatud arvu nullidega, kusjuures vajalik nullide arv sõltub ahela pikkusest.
Kuidas saadakse tingimusele vastav räsi? Teatavasti annab räsifunktsioon iga natukenegi erineva sisendi korral täiesti teistsuguse ja ettearvamatu räsi. Niisiis muudetakse loodava ploki sisu seni, kuni räsi muutub nõutud tingimusele vastavaks. Selleks on tavaliselt igas plokis salvestatud üks täiendav arvmuutuja (nimetatakse nonce), mille väärtust suurendatakse ühe võrra, kuni tekib sobiv räsi. Ühe ploki sobiva nonce-väärtuse leidmine võib välja näha umbes nii:
# Uus plokk viitega eelmise ploki räsile (identifikaatorile) nonce = 0 plokk = (andmed, eelmise_ploki_räsi, nonce) ploki_räsi = räsifunktsioon(plokk) # Muuda nonce väärtust, kuni tekib sobiv räsi while not on_sobiv(ploki_räsi): nonce += 1 plokk = (andmed, eelmise_ploki_räsi, nonce) ploki_räsi = räsifunktsioon(plokk) # Sai sobiv, lisa plokk plokiahelasse lisa_ahelasse(plokk) # Järgmine plokk hakkab hoidma eelmise ploki räsi eelmise_ploki_räsi = ploki_räsi
Tingimusele vastava räsi leidmise protsess võib kaua aega võtta. Kuna plokke luuakse pidevalt juurde, siis ei ole tagantjärele plokkide võltsimisega uusima plokini väljajõudmine praktiliselt võimalik.
Plokiahelaga saab mitmekülgsemalt tutvuda aines "LTAT.05.021 Sissejuhatus Blockchaini tehnoloogiasse".
Sõnumite vahetamine
Reaalajas toimuva edasi-tagasi andmete vahetamise jaoks kasutatakse tihti mõlemat krüpteerimisliiki ja räsimist. Asümmeetriline krüpteerimine on üldiselt aeglasem ja ressurssinõudlikum, mistõttu seda kogu aeg ei kasutata. Selle abil võidakse nt. alguses kokku leppida ühine sümmeetriline võti. Kuna kasutajate avalikud võtmed on teada, siis suhtlusprotsess võib alata niimoodi:
- Kõigepealt luuakse saatja poolel sümmeetrilise krüpteerimise jaoks ühine võti ning krüpteeritakse see saaja avaliku võtmega.
- Võtmest tehakse eraldi ka räsi ning allkirjastatakse saatja salajase võtmega.
- Krüpteeritud võti ja signeeritud räsi saadetakse teisele kasutajale.
- Saadud krüpteeritud võti dekrüpteeritakse saaja salajase võtmega.
- Signeeritud räsi dekrüpteeritakse saatja avaliku võtmega ja kontrollitakse, kas see ühtib saadud võtme räsiga.
- Nüüd on mõlemal osapoolel järgmiste sõnumite (de)krüpteerimiseks ühine võti, millega saab juba kiiremini toimetada.
Saatja pool:
Saaja pool:
Kui salajased võtmed on tõesti teada ainult nende omanikele, siis pärast neid samme võib üsna kindel olla saatja isikus ning selles, et keegi ei muutnud ega lugenud sõnumeid tee peal. Edaspidised sõnumid krüpteeritaksegi mõlemas suunas ainult jagatud võtmega, mis on teistele teadmata:
Võime sellise suhtlusskeemi teekide rsa
ja cryptography
abil läbi teha. Teegi rsa
abil luuakse asümmeetrilise krüpteerimise jaoks kasutajate võtmepaarid ja teegist cryptography
kasutatakse klassi Fernet
, et luua võti sõnumite sümmeetriliseks krüpteerimiseks. Räsimise ja räside kontrollimisega siin ise vaeva nägema ei pea, sellega tegelevad funktsioonid rsa.sign
ja rsa.verify
:
from cryptography.fernet import Fernet import rsa # RSA algoritmi võtmed ja ühisvõti avalikvõti_1, salavõti_1 = rsa.newkeys(1024) # Saatja võtmed avalikvõti_2, salavõti_2 = rsa.newkeys(1024) # Saaja võtmed ühisvõti = Fernet.generate_key() # Ühine võti krüpteeritakse saaja avaliku võtmega krüpteeritud = rsa.encrypt(ühisvõti, avalikvõti_2) # Ühine võti räsitakse ja signeeritakse saatja salajase võtmega signeeritud = rsa.sign(ühisvõti, salavõti_1, "SHA-256") ... # Signeeritud võtmeräsi ja krüpteeritud võti edastatakse # Saaja dekrüpteerib saadud võtme oma salajase võtmega dekrüpteeritud = rsa.decrypt(krüpteeritud, salavõti_2) # Kontrollitakse, kas signeeritud räsi ühtib saadud võtme räsiga rsa.verify(ühisvõti, signeeritud, avalikvõti_1) # Annab veateate, kui ei ühti # Edaspidi võivad mõlemad osapooled sõnumeid krüpteerida ühise võtmega # Võtit ja klassi Fernet kasutatakse nüüd šifri loomiseks, mille abil saab baite (de)krüpteerida siffer = Fernet(ühisvõti) sõnum = "Tere!".encode("utf-8") # Saadetav sõnum (kodeeritud) krüpteeritud = siffer.encrypt(sõnum) ... dekrüpteeritud = siffer.decrypt(krüpteeritud) sõnum = dekrüpteeritud.decode("utf-8") # Kättesaadud sõnum (dekodeeritud)
Otspunktkrüpteerimine
Kui andmevahetuse puhul on tagatud, et (de)krüpteerimine toimub ainult kahe osapoole seadmetes, siis tohib kasutada terminit otspunktkrüpteerimine (ingl end-to-end encryption). See tähendab, et mitte keegi teine, kaasa arvatud teenusepakkuja, pole andmeid kuskil tee peal (de)krüpteerinud.
Enesekontrolliküsimused
Ülesanded
1. Kirjuta programm, mis küsib kasutajalt tekstifaili nime ja kodeeringut ning soovitud kodeeringut. Programm peaks seejärel avama faili õige kodeeringuga ja salvestama selle soovitud kodeeringusse. Raskem variant: Programm peab ise püüdma mingite tunnuste abil ära arvata, mis kodeeringus fail salvestatud on. Enne faili ümbersalvestamist võiks siiski kasutajalt üle küsida, kas pakkumine on õige.
2. Koosta programm, millega saab faile krüpteerida ja dekrüpteerida. Krüpteeritud failid võiks salvestada nt. faililaiendiga crypted
, nii et ka algne laiend säiliks:
tekstifail.txt -> tekstifail.txt.crypted
Programm peaks kõigepealt küsima kasutajalt faili nime või asukoha ning krüpteerimise võtme.
- Kui faililaiend ei ole
crypted
, siis programm krüpteerib faili sisu antud võtme abil ning salvestab faili uue laiendiga. Kui kasutaja võtit ei sisestanud, genereerib programm ise võtme ning salvestab selle pärast eraldi faili. - Kui fail on krüpteeritud, peaks küsima krüpteerimiseks kasutatud võtit ning dekrüpteerima faili algsele kujule.
Vihje: Faili sisu on baitidena kõige mugavam kätte saada või salvestada nii:
with open("fail.txt", "rb") as f: baidid = f.read() with open("fail.txt.crypted", "wb") as f: f.write(baidid)
3. Tee enda jaoks turvaline paroolide haldamise programm, mis nõuab andmetele ligipääsemiseks kõigepealt kasutajalt peaparooli. Seejärel võiks programm pakkuda võimalust kuvada paroole või lisada uusi.
- Kui kasutaja tahab lisada sissekande, peaks programm küsima konto nime ja/või veebilehe ning vastava parooli. Need tuleks seejärel krüpteeritud kujul faili salvestada.
- Kui kasutaja tahab paroole kuvada, peaks lugema failist krüpteeritud sissekanded ning need dekrüpteeritult kuvama.
Otsusta ise, mis kujul andmeid hoida ja krüpteerida, kus räsimist kasutada ning kuidas peaparooliga toimetada!
Vihje: Kui soovid paroole turvaliselt süsteemi käsurealt sisestada, siis selleks on moodul getpass
. Thonny ega IDLE käsureal see ei tööta.
4. Loe ka programmidevahelise suhtluse kohta ja tee sellised programmid, mis suhtlevad omavahel krüpteeritud sõnumitega.