Lohkosalain

Tässä osiossa opimme lohkosalaimen toimintaperiaatteen.

Tämä osio on viimeinen kerta kurssilla, kun luomme itse periaatteessa oikein toimivia, mutta todennäköisesti turvattomia kryptografisten algoritmien prototyyppejä. Tämän vuoksi tässä osiossa kysymykset painottuvat hieman enemmän annettujen koodien ymmärtämiseen.

Yksi tehtävien tavoite on auttaa hahmottamaan, että myöhemmissä opetusmoduuleissa käyttämämme kryptografiset kirjastot tekevät paljon tarkistuksia ja muunnoksia datalle sekä avaimille.


Kuten videossa kerrotaan, lohkosalaimessa on kolme toiminnallisuutta:

  • datan lohkoihin jakava toiminnallisuus

  • salain, joka enkoodaa tai dekoodaa yksittäisen lohkon

  • lohkojen yhdistäjä, joka kokoaa salaimen tuottamat lohkot kokonaisuudeksi

Enkoodaus ja dekoodaus, eli tässä tapauksessa salaus ja purku, käyttävät samaa koneistoa.


Yksinkertainen lohkosalain

Alla on koodiesimerkki, joka toteuttaa yksinkertaisen lohkosalaimen merkkijonolle. Funktiolle annettu merkkijono operoidaan kirjainmerkki kerrallaan XOR-operaatiolla avaimen kanssa. Jokaisen kirjaimen oletetaan olevan koodattuna 8 bitin eli yhden tavun kokoinen.

Aja koodi seuraavassa solussa. Siinä kiinnitämme avaimet sekä näytämme salattavan informaation, eli merkkijonon, sisällön. Jos tehtävät vaativat avaimen tai viestin muuttamista, on muutokset tehtävä tähän seuraavaan koodisoluun. Kiinteän avaimen tai kertakäyttöluvun alustaminen algoritmin testivaiheessa on ok.

  • Kiinnitämme 8-bittiseksi k1-avaimeksi heksatavun 0x6A

  • Kiinnitämme 64-bittiseksi k2-avaimeksi 0xA1B2C3D4E5F60718.

Kannattaa huomata, että 8-bittinen avain on tyypiltään integer ja 64-bittinen on string. Tämä valinta on tehty vain sen takia, että voimme käsitellä 64-bittistä avainta ascii-merkkeinä joissain tehtävissä. k2 voisi suoraan olla k2=0xA1B2C3D4E5F60718.

Luonnollisesti voisimme käyttää kryptografisesti vahvaa satunnaislukua avaimena, mikä onkin kryptografiassa oikea tapa. Tämä kuitenkin vaikeuttaisi algoritmin toiminnan demonstroimista ja yksityiskohtien selvittämistä esimerkkitehtävissä.

Inactive
# Aktivoidaan kirjastot joita käytämme
from numpy import binary_repr
import binascii

# Kuten tiedämme yksi tapa tuottaa oikea avain on käyttää entropia-allasta: secrets.randbelow(2**bittejä)
# Tässä esimerkissä lukitsemme avaimen, jotta voimme selittää bittitasolla mitä tapahtuu

# Lukitaan avaimet
k1 = 0x6A
k2 = "0xA1B2C3D4E5F60718"

# Näytetään avaimet
print(f" 8-bittinen avain, jota käytämme yksinkertaisessa lohkosalaimessa: 0x{k1:X}")
print("64-bittinen avain, jota käytämme yleisessä lohkosalaimessa on:",k2)

# Käytämme viestinä vanhaa tuttua virkettä
viesti = "Kahvi Charlotassa on hyvää ja vahvaa!"
print("Viestimme kuuluu:", viesti)

Seuraava koodisolu luo yksinkertaisen lohkosalaimen. Lohkosalain ottaa prosessoitavaksi dataksi merkkijonomuodossa olevan informaation ja avaimen.

Tämän yksinkertaisen lohkosalaimen lohkoja ovat merkkijonon yksittäiset merkit. Esimerkiksi ”Kissa”, prosessoitaisiin lohkoina ’K’, ’i’, ’s’, ’s’ ja viimeisenä ’a’.

Lohkosalain pystyy enkryptaamaan ja dekryptaamaan annetun merkkijonon avaimella. Menetelmä on symmetrinen, ja käytämme salainta siten, ettei meidän tarvitse kertoa olemmeko enkryptaamassa vai dekryptaamassa.

  • Alla oleva koodi luo ensin yksinkertaisen salaimen

  • Sen jälkeen salataan teksti ”Kissa” avaimella 0x6A ja salattu lohko näytetään.

  • Salauksen lopuksi näytetään ”Kissa” salattuna, jolloin näkyviin tulee symboleita.

  • Tämän jälkeen salaus puretaan samalla avaimella 0x6A lohkoittain ja purettu lohko näytetään.

  • Salauksen purun jälkeen näytetään saatu selväkielinen viesti. Sen pitäisi olla ”Kissa”

Inactive
from numpy import binary_repr

#
# Luodaan yksinkertainen lohkosalain
#
def yksinkertainen_lohkosalain(merkkijono, avain, näytälohkot=False):
    """Yksinkertainen lohkosalain demo-funktio
       Parametrit:
       merkkijono (string): Lohkosalaimella enkoodattava tai dekoodattava tieto, oletus "latin-1" koodattu
       avain (int): Symmetrinen avain, jota käytetään enkoodauksessa ja dekoodauksessa
       näytälohkot (bool): Lippu, jolla määritetään näytetäänkö lohkosalauksen välivaiheet.

       Palauttaa:
          string: Enkoodattu tai dekoodattu merkkijono.
    """
    # Salaimissa on useita tarkistuksia suojattavan tiedon tyypeille sekä annettujen parametrien hyvyyksille.
    # Nämä tarkistukset ja algoritmin sisäiset tyyppimuunnokset ovat salaimen käyttäjälle näkymättömiä.

    # Seuraavassa muutetaan merkkijonon ASCII merkit tavuiksi, olettaen että merkkijono on 'latin-1' merkistö-koodattua.
    # Tämä muunnos ei välttämättä toimi muilla merkki-koodauksilla.
    tavutettu_merkkijono = bytes(merkkijono, encoding="latin_1")

    # Luodaan paikka enkoodauksen tai dekoodauksen tulokselle
    XOR_tulos = []

    # Pilkotaan merkkijono lohkoiksi. Jokainen tavu (eli merkki) muodostaa oman lohkon.
    for lohko in tavutettu_merkkijono:
        # Suoritetaan salaimen matemaattinen operaatio (XOR) tavuittain avaimen kanssa.
        XOR_tulos.append(avain ^ lohko)

        # Jos haluat nähdä miten lohko käsitellään aseta funktiokutsuun näytälohkot=True
        if näytälohkot:
            print("Lohko on {}, avain {} ja XOR {}".format(
                chr(lohko), hex(avain), binary_repr(avain ^ lohko, 8)))

        # Muunnetaan salaimen tulos merkkijonoksi, yleensä haluamme käsitellä vain bittejä ja tavuja.
        # Tässä esimerkissä käsittelemme kirjain-merkkejä, joten demonstraation vuoksi muunnamme salaimen tuottaman datan merkkijonoksi.
        XOR_merkkijonona = "".join([chr(lohko) for lohko in XOR_tulos])

    # Palautetaan merkkijonona.
    return XOR_merkkijonona

#
# -- käytetään salainta -- #
#

selväkieli = "Kissa"
tehtäväavain = 0x6A

print("Salataan sana \"{}\", näytetään lohkot:".format(selväkieli))
salakieli=yksinkertainen_lohkosalain(selväkieli, tehtäväavain, näytälohkot=True)
print("{} salattuna on:{}\n".format(selväkieli, salakieli))

print("Puretaan salaus, näytetään lohkot")
purettu_salakieli = yksinkertainen_lohkosalain(salakieli, tehtäväavain, näytälohkot=True)
print("Salauksen purun tulos on:{}\n".format(purettu_salakieli))

if purettu_salakieli == selväkieli:
    print("Jee saimme salattua ja purettua {}-sanan".format(selväkieli))
else:
    print("Jokin meni pieleen.")

Edellisessä koodissa käytimme funktiokutsussa argumenttia näytälohkot, jolla näemme miten salaimen koneisto lohkoo sanan ”Kissa” 8-bittisiksi lohkoiksi. Funktiosta toimii seuraavasti:

  1. Funktio olettaa että annettu informaatio on tekstiä ja muuttaa sen tavukoodiksi.

  2. Funktio lohkoo tavukoodimuodossa olevan informaation 8-bittisiksi lohkoiksi (eli tavuiksi).

  3. Varsinainen lohkon salaus tapahtuu kohdassa avain^lohko.

  4. Lopuksi tuotetut lohkot yhdistetään, ja yritämme näyttää informaatiota ascii-merkkeinä.

Seuraavassa koodisolussa käytämme tätä yksinkertaista lohkosalainta.

  • Otamme salaimen käyttöön kirjastosta, kuten myös tehtävään kuuluvan viestin ja avaimen.

  • Näytämme viestin ja avaimen.

  • Enkoodaamme (eli salaamme) viestin lohkot (eli merkit) avaimella k1.

  • Yhdistämme lohkot salakirjoitetuksi merkkijonoksi.

  • Näytämme salatun viestin, jossa pitäisi olla näkyvissä symboleita kirjaimien sijaan.

  • Lopuksi suoritetaan dekoodaus (eli salakirjoituksen purku) lohkoittain avaimella k1.

Inactive
from xip import alusta_t542

viesti, yksinkertainen_lohkosalain, k1, _ = alusta_t542()

print("Viesti on  :", viesti)
print("Avain k1 on:", k1)

# Suoritetaan salaus yksinkertaisella lohkosalaimella viestille avaimella k1
salakirjoitus_merkkijono=yksinkertainen_lohkosalain(merkkijono=viesti, avain=k1)
print("Salattu viesti on  :", salakirjoitus_merkkijono)

# Suoritetaan salauksen purku yksinkertaisella lohkosalaimella avaimella k1
purettu_salakirjoitus=yksinkertainen_lohkosalain(merkkijono=salakirjoitus_merkkijono, avain=k1)
print("Purettu viesti on  :",purettu_salakirjoitus)

Vastaa edellisen koodisolun pohjalta kysymyksiin.

Tehtävän viesti on:

Tehtävän avain on:

Valitse kaikki oikeat vaihtoehdot. Tehtävän salatussa viestissä:

Kun olemme suorittaneet enkoodauksen ja dekoodauksen, niin dekoodattu viesti on:

Voit kokeilla koodin toimintaa vaihtelemalla sekä avainta että viestiä. Voit käyttää salainta suoraan ja enkoodata vapaavalintaisen viestin haluamallasi avaimella:

Inactive
from xip import yksinkertainen_lohkosalain

# Voit muuttaa seuraavien muuttujien arvoja seuraavaan tehtävään, esim salaus_avain = 0xAB
viestini = "::::Tärkeä salattava viesti:::: TF:n lihapullat on hyviä!"
salaus_avain = 0xFF
purku_avain = 0xA5

salattu_viesti = yksinkertainen_lohkosalain(merkkijono = viestini, avain=salaus_avain, näytälohkot=False)
purettu_viesti = yksinkertainen_lohkosalain(merkkijono = salattu_viesti, avain=purku_avain, näytälohkot=False)

print("Selvä  :",viestini)
print("Salattu:",salattu_viesti)
print("Purettu:",purettu_viesti)

Seuraaviin väittämiin vastaaminen voi vaatia, että muokkaat edellistä koodisolua. Vastaukset on mahdollista myös päätellä, mutta koodin muokkaaminen on helpompaa.

Mitä tapahtuu, jos käytät eri avainta enkoodauksessa ja dekoodauksessa? Voit joko päätellä vastaukset tai kokeilla avaimen arvoja edellisessä koodisolussa.


Onko yksinkertainen lohkosalain taipumaton?

Yksi hyvän salaimen ominaisuuksista on taipumattomuus (engl. non-malleability ). Tämä tarkoittaa sitä, että jos salaimelle annettavaan dataan tai avaimeen tehdään muutos, ei muutos saa näkyä merkityksellisellä tavalla salaimen tuottamassa datassa.

Testataan, kuinka hyvä yksinkertainen lohkosalaimemme on. Lohkosalaimemme käsittelee yksittäisiä merkkejä lohkoina. Alla olevassa tekstissä sanoja merkki ja lohko käytetään synonyymeinä.

  • viesti1 on jo aikaisemmista koodiesimerkeistä tuttu viesti.

  • viesti2:ssa ensimmäinen h-kirjain on korvattu i-kirjaimella. Näiden kirjaimien bittiero on yksi bitti. Voit tarkastaa esimerkiksi h-kirjaimen bittikuvion ajamalla seuraavan koodin: print(bin(ord('h')))

  • viesti3:ssa ensimmäinen a-kirjain on korvattu q-kirjaimella. Näiden kirjaimien bittiero on myös yksi bitti, mutta muuttunut bitti sijaitsee eri kohdassa.

Viestien luomisen jälkeen näytetään, mikä ero viesteillä 1 ja 2 sekä 1 ja 3 välillä on.

  • Luomme kaksi 8-bittistä avainta ka ja kb.

  • Näiden avainten ero on yksi bitti (LSB-päässä eli oikealla).

  • Tämä näytetään tulostamalla XOR avaimista ka^kb.

Sen jälkeen muodostamme neljä salakirjoitusta:

  • sk1 on muodostettu viesti1:stä avaimella ka

  • sk2 on muodostettu viesti1:stä avaimella kb

  • sk3 on muodostettu viesti2:sta avaimella ka

  • sk4 on muodostettu viesti3:sta avaimella ka

Lopuksi näytetään miten viestit poikkeavat.

Inactive
from numpy import binary_repr
from xip import yksinkertainen_lohkosalain

# Luodaan kolme viestiä
viesti1 = "Kahvi Charlotassa on hyvää ja vahvaa!"
viesti2 = "Kaivi Charlotassa on hyvää ja vahvaa!"
viesti3 = "Kqhvi Charlotassa on hyvää ja vahvaa!"

# Näytetään mikä ero on viesti1:llä ja viesti2:lla.
for m1,m2 in zip(viesti1, viesti2):
    if m1!=m2:
        print("\nViestien 1 ja 2 lohkot eroavat ja ero on:")
        print("1:n lohkon merkki:n \033[1;30;48m{}\033[0;0m ".format(m1),end="")
        print("ja 2:n lohkon merkki: \033[1;30;48m{}\033[0;0m ".format(m2),end="")
        print("ja ero bittikuviona 0b{}.".format(binary_repr(ord(m1)^ord(m2),8)))

# Katsotaan mikä ero on viesti1:llä ja viesti3:lla
for m1,m2 in zip(viesti1, viesti3):
    if m1!=m2:
        print("\nViestien 1 ja 3 lohkot eroavat ja ero on:")
        print("1:n lohkon merkki:n \033[1;30;48m{}\033[0;0m ".format(m1),end="")
        print("ja 3:n lohkon merkki: \033[1;30;48m{}\033[0;0m ".format(m2),end="")
        print("ja ero bittikuviona 0b{}.".format(binary_repr(ord(m1)^ord(m2),8)))

# Luodaan kaksi avainta, joiden viimeinen bitti eroaa.
ka = 0b01011010
kb = 0b01011011

# Näytetään miten avain ka ja kb poikkeavat toisistaan suorittamalla XOR näiden välillä
print("\nAvaimen ka ja kb ero bitteinä on :0b{}".format(binary_repr(ka^kb,8)))

# Luodaan neljä salakirjoitusta
sk1=yksinkertainen_lohkosalain(merkkijono=viesti1, avain=ka)
sk2=yksinkertainen_lohkosalain(merkkijono=viesti1, avain=kb)
sk3=yksinkertainen_lohkosalain(merkkijono=viesti2, avain=ka)
sk4=yksinkertainen_lohkosalain(merkkijono=viesti3, avain=ka)

print("Neljä salakirjoitusta sk1, sk2, sk3 ja sk4 on luotu")

Seuraavissa soluissa ladataan samat muuttujat suoraan kirjastosta (arvot toki lasketaan kirjastofunktion sisällä). Tarkistetaan miten viestistä 1 kahdella eri avaimella ka ja kb tuotetut salakirjoituksen lohkot poikkeavat.

Koodi vertaa salattua informaatiota sk1 ja sk2 lohkoittain ja tulostaa jokaisen lohkon kohdalla näiden eron.

Inactive
from xip import alusta_t545
from numpy import binary_repr

v1, v2, v3, ka, kb, sk1, sk2, sk3, sk4 = alusta_t545()

# Salakirjoitukset 1 ja 2 on tuotettu samasta viestistä 'viesti1' kahdella eri avaimella ka ja kb.
# Lasketaan salakirjoitusten 1 ja 2 merkkien erot XOR-funktiolla ja näytetään eroavaisuus bitteinä.
for m1, m2 in zip(sk1, sk2):
    print("Salattujen lohkojen ero bitteinä:0b{}".format(binary_repr(ord(m1)^ord(m2),8)))

print("\nJokainen salattu lohko poikkeaa yhdellä bitillä")
print("Tämä on sama ero kuin avaimilla:0b{}".format(binary_repr(ka^kb,8)))

Seuraavaksi tarkistetaan miten selväkielen viesteistä 1 ja 2 samalla avaimella ka tuotetut salakirjoitukset 1 ja 3 (sk1 ja sk3) poikkeavat.

Inactive
from xip import alusta_t545
from numpy import binary_repr

v1, v2, v3, ka, kb, sk1, sk2, sk3, sk4 = alusta_t545()

# Salakirjoitukset 1 ja 3 on tuotettu samalla avaimella ka.
# Viesti 1 eroaa viestistä 3 siten, että yksi merkki on muutettu yhtä LSB-bitin muunnosta vastaavasti.
# Lasketaan salakirjoitusten 1 ja 2 merkkien erot XOR-funktiolla ja näytetään eroavaisuus bitteinä.
for m1, m2 in zip(sk1, sk3):
    if m1!=m2:
        print("Salattujen lohkojen ero bitteinä:0b{}".format(binary_repr(ord(m1)^ord(m2),8)))
    else:
        print("Lohkot identtiset")

print("\nSelväkirjoitus viestien 1 ja 3 eroa on yksi bitti yhdessä merkissä")
print("Tämä ero näkyy suoraan salakirjoituksissa yhden lohkon yhden bitin erona.")

Lopuksi tarkistetaan, miten selväkielen viesteistä 1 ja 3 (v1, v3) samalla avaimella ka tuotetut salakirjoitukset 1 ja 4 (sk1 ja sk4) poikkeavat.

Inactive
from xip import alusta_t545
from numpy import binary_repr

v1, v2, v3, ka, kb, sk1, sk2, sk3, sk4 = alusta_t545()

# Selväkielen viestien 1 ja 3 ero on yksi bitti yhdessä merkissä.
# Salakielet 1 ja 4 on tuotettu samalla avaimella ka.

for m1, m2 in zip(sk1, sk4):
    if m1!=m2:
        print("Salakirjoitus lohkojen ero bitteinä:0b{}".format(binary_repr(ord(m1)^ord(m2),8)))
    else:
        print("Lohkot identtiset")

print("Salakirjoituksissa on eroa vain yhden bitin verran, kuten selväkielen viesteissä.")

Käytimme yllä lohkosalainta kolmella viestillä, jotka keskenään keskenään vain hieman, sekä kahta avainta, jotka poikkeavat yhdellä bitillä. Viestit ja avaimet oli valittu edellisiin esimerkkeihin niin, että selväkielen viesteissä ja avaimissa on pienin mahdollinen ero, eli 1:n bitin ero.

Tutki edellisten koodisolujen tulosteita ja vastaa väitteisiin.

Väittämät koskevat yksinkertaista lohkosalainta. Valitse oikeat vaihtoehdot.

Seuraava 64-bittinen lohkosalain kärsii samasta ongelmasta. Käytetty bittimäärä ei vaikuta siihen, että kehnon lohkosalaimen sisäänmenevän ja ulostulevan informaation välillä on selkeä yhteys.


64-bittinen lohkosalain

Aiemmilla opetuskerroilla olet käyttänyt valmiita funktioita, joiden sisällä olevaa koodia ei ole esitelty. Seuraavaksi muunnamme aiemmin luomamme 8-bittisen lohkosalaimen toimimaan 64-bittisenä. Näet koodista, että yksinkertaisenkin lohkosalauksen tekeminen vaatii tiettyjä välivaiheita ja muunnoksia. Koodin pitäisi myös tarkistaa muuttujatyypit ja huolehtia ettei koodin suoritusaika poikkea eri avaimilla. Kaikki nämä oikean maailman vaatimukset kryptografisten funktioiden hyvyyksistä on jätetty nyt pois.


Tämän osuuden jälkeen emme enää rakenna esimerkkifunktioita itse. Tärkeä periaate:

KÄYTÄ VALMIITA TESTATTUJA FUNKTIOITA KRYPTOGRAFISISSA OPERAATIOISSA - ÄLÄ TEE FUNKTIOITA ITSE.


Lohkosalaimemme lohkon koko on 64 bittiä. Tämä tarkoittaa sitä, että salaimelle tuleva data käsitellään 64 bitin kokoisina paloina. Merkkijonoon pohjautuva esimerkkisalaimemme lohko täytetään 8 tavulla, joista jokainen on 8 bittiä (8 tavua x 8 bittiä tavussa = 64 bittiä).

Joudumme nyt lohkotuksen ja lohkojen yhdistämisen lisäksi takaamaan, että lohko täyttyy.

  • Jos informaatiota on lohkon kokoa vähemmän, on lohko täytettävä. Salaimelle tuotetaan aina ”täysi lohko” dataa enkoodattavaksi tai dekoodattavaksi.

  • Jos lohkoon pitää keinotekoisesti tuottaa sisältöä, niin tällaista operaatiota kutsutaan päddäykseksi (engl. padding). Alla on näytetty esimerkin omaisesti miten, tällainen päddäys voitaisiin tehdä.

Päddäys suoritetaan seuraavasti:

  • Tässä omassa päddyksessämme lisäämme annetun datan perään tavuja järjestyksessä alkaen 0x00:sta aina 0x06:n asti, kunnes lohko on täynnä.

  • Jos informaatiossa on seitsemän merkkiä, esimerkiksi ”91AB4d?”, pädätään siihen yksi tavu 0x00, jolloin lohkossa on kahdeksan tavua.

  • Jos informaatiossa on esimerkiksi viisi kirjainta, esimerkiksi ”ABCDE”, täytyy informaation perään pädätä 0x00 + 0x01 + 0x02, jolloin lohko täyttyy kahdeksaan tavuun.

  • Samoin jos informaatiossa on vain yksi kirjain, esimerkisi ”Ä”, siihen pädätään 0x00 + … + 0x06, jolloin saavutetaan kahdeksan tavua.

Päddäys pitää myös pystyä poistamaan. Tähän on tehty oma funktio, joka käy dekoodatun informaation läpi ja palauttaa datan siihen asti kunnes kohtaa arvon 0x00. Tällainen päddäys on melko heikko ja haavoittuvainen tietyille hyökkäyksille. Tässä omassa salaimessamme suoritamme päddäyksen ennen enkoodausta ja poistamme päddäyksen dekoodauksen jälkeen.

Enkoodatessa lohko päddäyksen jälkeen muunnetaan biteiksi, jolle suoritetaan avaimella XOR. Salain suorittaa operaation kiinteällä 64-bittisellä avaimella k2 .

Alla olevat funktiot on tuotu nähtäville, mutta ne ladataan myöhemmin kirjastosta.

Inactive
# Tätä funktiota käytetään kun selvätekstiin lisätään merkkejä lohkon täyttämiseksi.
def päddäys(merkkijono, lohkon_koko=64, merkistö='latin-1'):
    """ Päddäys-funktio, lisää merkkijonon loppuun heksoja 0x00, 0x01, ... kunnes lohkon koko bitteinä täyttyy
    Parametrit:
        merkkijono (string): Merkkijono johon lisätään päddäys, oletus "latin-1"-merkistökoodaus
        lohkon_koko (int): Lohkon koko bitteinä, oletus 64-bittiä.

    Palauttaa:
        string: Merkkijono, jonka bittimäärä on jaollinen lohkon koon bittimäärällä.
    """
    mj_pituus_bits = len(merkkijono.encode(merkistö))*8
    return merkkijono + "".join([chr(laskuri) for laskuri in range(int((lohkon_koko - (mj_pituus_bits)%lohkon_koko)/8))])

# Tätä funktiota käytetään poistamaan päddäys dekryptatusta merkkijonosta.
def päddäyksen_poisto(merkkijono, pad_alku='\x00'):
    """ Päddäyksen poisto -funktio, palauttaa merkkijonon ilman päddäystä
    Parametrit:
        merkkijono (string): Merkkijono johon lisätään päddäys, oletus "latin-1"-merkistökoodaus
        pad_alku (chr): Tunniste mikä määrittää milloin päddäys alkaa.

    Palauttaa:
        string: Merkkijono, jossa ei ole enää päddäystä.
    """

    return merkkijono[:merkkijono.find(pad_alku)]

print("Päddäyksen-funktiot luotu")

Seuraavassa koodi-lohkossa luomme yleisen lohkosalaimen. Tämä lohkosalain salaa merkkijonoja, mutta periaatteessa se voisi prosessoida mitä tahansa tavumuodossa olevaa dataa.

Inactive
# Lohkosalain olettaa että se saa tavujonon, jonka koko on jaollinen lohkon koolla.
def yleinen_lohkosalain(tavujono, avain, lohkon_koko=64, merkistö='latin-1',näytälohkot=False):

    assert (len(tavujono)*8)%lohkon_koko == 0, "Päddäys unohtunut"

    # Tarvittaessa muutetaan merkkijono tavujonoksi 'latin-1' merkistökoodauksella
    if type(tavujono) is str:
       bytejono = tavujono.encode(merkistö)

    # Jos avain on merkkijono (heksadesimaaleja), muunnetaan avain luvuksi
    if type(avain) is str:
        avain = int(avain,16)

    # Paikka enkoodatulle viestille
    XOR_tulos_merkkijonona = ""

    # Lohkotaan bytejono siten että jokainen tavu muodostaa oman lohkon

    for lohkon_numero in range((len(bytejono)*8)//lohkon_koko) :
        # Otetaan yksi lohko ja tehdään siitä 64-bittinen luku
        lohko = bytejono[(lohkon_numero*8):((lohkon_numero+1))*(lohkon_koko//8)]
        # Ja tehdään lohkosta yksi 64-bittinen luku
        lohko_int = int.from_bytes(lohko ,"big")


        # Lasketaan siitä XOR avaimen kanssa.
        lohkon_xor = lohko_int^avain

        # Muunnetaan tulos tavujonoksi
        lohkon_xor_bytes = lohkon_xor.to_bytes(8,'big')

        # Tallenetaan XOR tulos listaan.
        XOR_tulos_merkkijonona += "".join([chr(tavu) for tavu in lohkon_xor_bytes])


        if näytälohkot:
            print("Lohkon numero   :", lohkon_numero)
            print("Lohkon sisältö  :", lohko)
            print("Lohkon xor      :", lohkon_xor)
            print("Lohkon xor Bytes:", lohkon_xor_bytes)
            print("Merkkijono nyt  :", XOR_tulos_merkkijonona)

    return XOR_tulos_merkkijonona

print("Yleinen merkkijonoilla toimiva lohkosalain luotu.")

Käytetään seuraavaksi yleistä salainta. Aloitetaan suorittamalla päddäys viestille.

Inactive
from xip import alusta_t546

viesti, päddäys = alusta_t546()

# Suoritetaan viestille päddäys, eli tehdään siitä sellainen että viimeinenkin lohko täyttyy.
pädätty_viesti= päddäys(viesti)
print("Viesti ennen päddäystä   :", viesti)
print("Viesti päddäyksen jälkeen:", pädätty_viesti)

print("\nAlkuperäisen viestin perään on lisätty tulostumattomia täytemerkkejä")

Sitten salaamme viestimme avaimella k2.

Inactive
from xip import alusta_t547

pädätty_viesti, k2, yleinen_lohkosalain = alusta_t547()

salattu_yleisellä_lohkosalaimella = yleinen_lohkosalain(pädätty_viesti, k2)
print("64-bittisellä lohkosalaimella enkoodauksen tulos on:", salattu_yleisellä_lohkosalaimella)

Ja lopuksi puretaan salattu viesti ja verrataan onko se sama kuin ennen salausta.

Inactive
from xip import alusta_t548

viesti, salattu_yleisellä_lohkosalaimella, k2, yleinen_lohkosalain, päddäyksen_poisto = alusta_t548()

# Dekoodataan viesti salaimella käyttäen avainta k2.
purettu_yleisellä_lohkosalaimella_pädding = yleinen_lohkosalain(salattu_yleisellä_lohkosalaimella, k2)

# Poistetaan päddäys dekoodatusta viestistä.
purettu_yleisellä_lohkosalaimella = päddäyksen_poisto(purettu_yleisellä_lohkosalaimella_pädding)

# Näytetään miltä purettu viesti näyttää.
print("64-bittisellä lohkosalaimella dekoodauksen tulos on:", purettu_yleisellä_lohkosalaimella)

if purettu_yleisellä_lohkosalaimella == viesti:
    print("Meidän 64-bittinen salain tuntuu toimivan!")
else:
    print("Voi voi, 64-bittinen salain on rikki!")

Murra avain ja viesti

Nyt pääset itse kokeilemaan salauksen murtamista. Alice lähettää Bobille yleisellä lohkosalaimella salatun viestin. Hänen käyttämänsä lohkosalaimen lohkokoko on 64 bittiä. Tehtävänäsi on selvittää käytetty avain ja vastata sitten alla esitettyihin väittämiin.

Tuodaan salakirjoitettu viesti muuttujaan bin_viesti.

Inactive
# Tuodaan salateksti muuttujaan 'bin_viesti'
import glob

with open(glob.glob("Tekstit/*bin")[0], "r", encoding="utf_8") as file:
    bin_viesti = "".join(file.readlines())

print(bin_viesti)

Olemme antaneet avaimen tähän mennessä heksadesimaaleina. Alice ja Bob käyttävät kuitenkin ihan arkisia avaimia. Seuraava koodi muuttaa kahdeksanmerkkisen tavallisen tekstin merkkijono-heksadesimaaliksi, jota käytämme avaimena salaimessa.

Inactive
# Funktio palauttaa annetusta 8-merkkisestä salasanasta heksadesimaalimuodon (merkkijonona)
# Tätä palautettua merkkijonoa voi käyttää salaimen kanssa.

def merkit_heksamerkeiksi(avain, pituus=8):
    assert len(avain)==pituus, "merkkijonossa pitää olla kahdeksan merkkiä"

    heksa_avain="0x"
    for byte in bytes(avain, encoding='latin-1'):
        heksa_avain += str(hex(byte))[2:].upper()

    return heksa_avain

print("Funktio luotu")

Tehtävänäsi on selvittää, mitä salakirjoitetussa viestissä sanotaan. Selvittääksesi viestin on sinun selvitettävä, mitä 8-merkkistä avainta Alice ja Bob ovat käyttäneet. Avain on hyvin ilmeinen asiayhteydestä, mutta voit joutua tekemään hieman salapoliisintyötä. Voit hyödyntää heikon lohkosalaimemme taipuvaisuutta, eli muunnella yksittäisiä avaimen merkkejä ja kokeilla mitä saat tulokseksi.

  • Vihje 1: Avaimessa on vain isoja ja pieniä kirjaimia. Siinä ei siis ole numeroita eikä erikoismerkkejä.

  • Vihje 2: Voit hyödyntää alempaa löytyvän tehtävän viimeisen kysymyksen vastausvaihtoehtoja, koska salain ei ole taipumaton.

  • Vihje 3: Muista, että aivan dekoodatun viestin lopussa voi olla korkeintaan seitsemän päddäysmerkkiä 0x00…0x06.

  • Vihje 4: Muista, että päddäyksen_poisto, palauttaa merkkijonon siihen asti kunnes ensimmäinen 0x00 arvo esiintyy.

  • Vihje 5: Tätä tehtävää kannattaa tehdä yhdessä kaverin kanssa. Älkää kuitenkaan jakako suoraan vastauksia, koska se vie ilon häkkeröinnistä.

Inactive
from xip import alusta_t549
import glob

with open(glob.glob("Tekstit/*bin")[0], "r", encoding="utf_8") as file:
    bin_viesti = "".join(file.readlines())

merkit_heksamerkeiksi, yleinen_lohkosalain, _ = alusta_t549()

# 'avaimemme' muuttujaan sijoitetaan avain millä kokeillaan murtamista.
avaimemme = "Kokeilen"

# Muunnetaan avain heksa-merkkijonomuotoon.
hex_avaimemme = merkit_heksamerkeiksi(avaimemme)

# Suoritetaan dekoodaus
dekoodattu = yleinen_lohkosalain(bin_viesti, hex_avaimemme)

# Emme suorita nyt päddäyksen poistoa, koska lelu-päddäyksemme saattaa lyehtää
# dekoodattua viestiä kun sopiva "päddäys" osuu vahingossa kohdilleen.

# Näytetään mitä saimme tulokseksi.
print("Dekoodattu viestimme on:")
print(dekoodattu)

Tässä vielä ylimääräinen alustettu solu, johon voit kirjoitella koodia.

Inactive
from xip import alusta_t549
import glob

with open(glob.glob("Tekstit/*bin")[0], "r", encoding="utf_8") as file:
    bin_viesti = "".join(file.readlines())

merkit_heksamerkeiksi, yleinen_lohkosalain, _ = alusta_t549()

print("Vapaa koodauslaatikko kokeiluihin.")

Pystyt vastaamaan seuraaviin kysymyksiin joko arvaamalla tai selvittämällä avaimen ja siten salakirjoituksen sisällön.

Seuraaviin väittämiin saat vastaukset, kun saat purettu salakirjoituksen.

Viesti sisältää:

Mitkä väittämistä pitävät paikkansa?

Mitä lukee viestin lopussa?


Yhteenveto

Tässä kappaleessa esitelty lohkosalain on pelkkä prototyyppi, mutta sen kaltaisia toimintoja suoritetaan ihan oikeassa salauksessa.

Tässä osiossa esitelty lohkosalain:

  • Pilkkoo enkoodattavan ja dekoodattavan informaation lohkoiksi.

  • Vaatii että data täyttää lohkon.

  • Käyttää samaa avainta enkoodauksessa ja dekoodauksessa, eli on symmetrinen.

Aikaisemmassa moduulissa opimme klassisten salainten yhteydessä permutaatiosta ja substituutiosta. Kun lohkosalaimeen lisätään substituutio-permutaatio-verkko, on mahdollista tuottaa lohkosalain, joka on käytännössä kryptografisesti taipumaton.

Käytämme seuraavassa opetusmoduulissa yhtä tällaista lohkosalainta ja opimme, miten miten sellainen toimii käytännössä.

Seuraavaksi vielä lyhyt, mutta tärkeä osio aiheesta, jota sivuttiinkin tässä osiossa.

Palautusta lähetetään...