Tiivisteet



Tässä moduulissa etenemme seuraavasti:

  • Ensin käytämme opeteltavaa kryptografista primitiiviä, joka on tällä kertaa tiivistefunktio.

  • Sen jälkeen opettelemme, miten tiivistefunktiot toimivat ja miksi ne toimivat sillä tavalla.

Opimme, mitä tiivistefunktioilla tehdään, mihin käyttöön ne soveltuvat ja mihin käyttöön ne eivät sovellu.


Tiivistefunktioita (engl. HASH-functions) käytetään laskemaan tiiviste (engl. digest).

Tätä ennen olemme kurssilla keskittyneet tiedon salaamiseen. Tiedon salaamisen lisäksi kryptografian tavoitteita ovat tiedoneheyden (engl. integrity) ja autenttisuuden (engl. authenticity) varmistaminen. Nyt opimme uuden käsitteen, tiiviste, joka muodostetaan informaatiosta/datasta tiivistefunktion avulla. Tiivistettä käytetään, kun tarkistamme kryptografisin menetelmin informaation eheyttä ja autenttisuus.

Mikä tiiviste on?

  • Tiivistefunktiolla informaatista lasketaan tiiviste eli lukuarvo. Lukuarvon suuruus on yleensä joko 256, 384 tai 512 bittiä.

  • Käytämme tiivistettä kuin se olisi jonkun tiedon tarkistussumma. Tiiviste ei kuitenkaan ole tarkistussumma informaatioteoreettisesta näkökulmasta.

  • Tiviiste, joka on siis tietyn kokoinen luku, näytetään yleensä heksadesimaalisena.

  • Esimerkiksi teksti ”Hyvää Päivää!” tuottaa 256-bittisen tiivisteen: 0x85029dd784c8ff14c8c94c2b2d1ad9ac5a13f88db1be5e049e2399ff778495ac, jos käytämme SHA-256 tiivistefunkiota.

  • Jos edellisestä tekstistä puuttuisi huutomerkki, jolloin teksti olisi ”Hyvää Päivää”, sen tiiviste vastaavasti on: 0x8468c8b55df70e7c5b2a51294411483a706db7b85205acae7ef3d30489639b79.

  • Tiivisteistä on mahdollista havaita ovatko tekstit tai yleisesti ottaen tiedostot ja informaatio samoja.

Mihin siis tiivistettä käytännössä käytetään?

  • Tiivisteiden avulla voi tarkastaa, ettei informaatiota ole muutettu.

  • Tällöin voimme varmistaa informaation eheyden.

Käytännössä tiivisteestä on hyötyä, esimerkiksi kun:

  • Jos lataat kuvan sosiaalisesta mediasta, miten voit varmistaa, ettei joku ole muuttanut kuvaa sen jälkeen, kun se on alun perin laitettu esille?

  • Tiettyjä tiivistefunktioista johdettuja kryptografisia menetelmiä voi käyttää siten, että niiden tuottama tiiviste on muodostettu salaisella avaimella. Tällöin sekä tiedoston eheys että autenttisuus voidaan varmistaa kryptografisen algoritmin ja avaimen avulla.

  • Jos lataat PDF-dokumentin, jossa on tarjous uudesta aurinkovoimalasta, voit allekirjoitetun tiivisteen ansiosta olla varma siitä, että kyseessä on aito tarjous jonka on lähettänyt todellinen taho.


Esimerkkinä digitaalisesta informaatiosta toimii jälleen viesti. Viesti voi koostua monenlaisesta digitaalisesta datasta. Se voi olla esimerkiksi tekstitiedosto, kuva, äänite tai video.

Viestistä lasketaan tiivistefunktiolla (engl. hash-function) varsinainen tiiviste (engl. digest).

../_images/l6_tiivistefunktio.png

Tiiviste on yleensä tietty määrä bittejä, joita kuvataan heksadesimaalilukuina. Yksi tiivistefunktio tuottaa yleensä saman kokoisen tiivisteen. Kuten jo mainittiin, yleiset tiivisteiden koot ovat 256, 384 ja 512 bittiä. Muunkin kokoisia tiivisteitä on olemassa, mutta nämä ovat tiivisteiden yleisimmät koot.

Alla olevissa esimerkeissämme käytämme SHA256-tiivistefunktiota, joka tuottaa aina 256-bittisen eli 32-tavuisen tiivisteen. Tavuhan muodostuu 8-bitistä, joista saadaan kaksi heksadesimaalilukua.

Tiiviste näyttää voi näyttää esimerkiksi seuraavalta: 340e1bd57e10e955cb5c49a8a87da3dbe1e366bd140e86c4bd24e1902f1df357.

Kokeillaan miten tiiviste lasketaan Python-koodilla.


Vastaa aiemman tekstin mukaan väittämiin.

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

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



Esimerkkejä tiivisteen laskemisesta

Samoin kuin aiemmin, seuraava Python lohko esittelee koodin, joka myöhemmin tuodaan kirjastosta käyttöön.

Seuraavissa koodisoluissa teemme seuraavaa:

  • Ensiksi luomme kaksi tarvittavaa funktiota, joista ensimmäinen, laske_tiiviste, laskee tiivisteen ja toinen, testaa_tiivisteet, testaa kahden tiivisteen eron ja palauttaa eroavien bittien määrän.

  • Tämän jälkeen luomme neljä viestiä (a, b, c, d) ja laskemme niille tiivisteet. Tiivisteet esitetään heksadesimaaliarvoina.

  • Lopuksi testaamme paljonko tiivisteet eroavat, kun viestissä muuttuu vain yksi merkki.

Seuraava koodilohko luo funktion, joka laskee tiivisteen annetulle tekstille. Myöhemmissä koodisoluissa tämä koodi tuodaan kirjastosta käyttöön.

Inactive
# Otetaan käyttöön tiivistefunktioiden kirjasto
import hashlib

def laske_tiiviste(teksti, tekstipalaute=True, debug=False):

    # Testataan että annettu teksti on todellakin tekstimuotoinen
    assert isinstance(teksti, str), "Anna tiivistefunktiolle merkkijono!"

    # Muunnetaan merkkijono tavujonoksi:
    tavuviesti = teksti.encode()

    # Otetaan käyttöön SHA256 tiivistefunktio-olio, tästä puhumme seuraavissa kappaleissa
    sha256tiivistefunktio = hashlib.sha256()

    # Lasketaan tiiviste
    sha256tiivistefunktio.update(tavuviesti)

    # Muunnetaan tiiviste etumerkittömäksi 256 bittiseksi kokonaisluvuksi
    oikea_tiiviste_tavuina = sha256tiivistefunktio.digest()
    oikea_tiiviste_lukuna = int.from_bytes(oikea_tiiviste_tavuina, byteorder='big')

    if debug:
        # Näytetään teksti ja tiiviste heksana
        print("Teksti oli               :", teksti)
        print("Laskettu tiiviste heksana:", sha256tiivistefunktio.hexdigest())

    # Palautetaan tiiviste joko heksa-stringinä tai sitten tavuina eli byteinä
    if tekstipalaute:
        return sha256tiivistefunktio.hexdigest()
    else:
        return sha256tiivistefunktio.digest()

print("Tiivistefunktion laskenta luotu")

Yksinkertainen tiivisteen laskeminen

Katsotaan, miten tiivisteen laskeminen yksinkertaisimmillaan tapahtuu. Koodisolu hyödyntää aiemmin luotua laske_tiiviste -funktiota.

  1. Aja oheinen koodi ja tarkastele tulosteita.

  2. Vastaa tehtävän väittämiin.

Inactive
from xip import alusta_t700
laske_tiiviste, _ = alusta_t700()

viesti_a = "Tiivisteet ovat tärkeässä roolissa kryptografiassa."
tiiviste_a = laske_tiiviste(viesti_a)
viesti_b = "Tiivisteet ovat tärkeässä roolissa kryptografiassa!"
tiiviste_b = laske_tiiviste(viesti_b)

print("Viestin A tiiviste:", tiiviste_a)
print("Viestin B tiiviste:", tiiviste_b)

Tämä tehtävä olettaa että olet ajanut edellisen koodisolun.

Valitse oikea vaihtoehto.

Valitse oikea vaihtoehto.


Jatketaan esimerkkiä

Seuraava koodisolu luo funktion, joka laskee montako bittiä tiivisteet eroavat toisistaan.

Inactive
# Tämä funktio testaa kuinka monella bitilla tiivisteet eroavat toisitaan.
# Jos eroa on nolla, niin tiivisteet ovat samat

def testaa_tiivisteet(eka_tiiviste, toka_tiiviste):

    # Testataan että funktiolle annetut argumentit ovat oikeaa tieto typpiä
    assert isinstance(eka_tiiviste, bytes), "Ensimmäiseksi annettu tiiviste pitää olla tavumuodossa"
    assert isinstance(toka_tiiviste,bytes), "Toiseksi annettu tiiviste pitää olla tavumuodossa"

    # Testataan että tiivisteet ovat yhtä pitkiä
    assert len(eka_tiiviste)==len(toka_tiiviste), "Tiivisteet ovat erimittaisia, näitä ei voi testata"

    # Muunnetaan tavumuotoinen tiiviste etumerkittömäksi 256 bittiseksi kokonaisluvuksi
    eka_lukuna = int.from_bytes(eka_tiiviste, byteorder='big')
    toka_lukuna = int.from_bytes(toka_tiiviste, byteorder='big')

    # Lasketaan XOR, "ero"-luvussa jokainen '1'-bitti tarkoittaa että alkuperäisten tiivisteiden bitit olivat eri
    ero = eka_lukuna^toka_lukuna

    # Lasketaan ykkösbittien määrä
    tiiviste_bittien_ero = bin(ero).count('1')

    # Näytetään tiivisteiden ero
    print("Eroa tiivisteiden välillä on " + str(tiiviste_bittien_ero) + " bittiä")

    # Palautetaan tiivisteiden ero vielä numerona
    return tiiviste_bittien_ero

print("Tiivisteiden eron testausfunktio luotu")

Seuraavaksi luomme neljä viestiä (a, b, c ja d) ja laskemme niille tiivisteet.

Inactive
from xip import alusta_t700
laske_tiiviste, testaa_tiivisteet = alusta_t700()

# Tässä esimerkissä luodaan neljä viestiä:
# A ja B viestit eroavat vain yhden merkin verran
# C viesti on lyhyt
# D viesti on pitkä

viesti_a = "Syntymäpäiville olisi löydettävä hyvä täytekakku!"
viesti_b = "Syntymäpäiville olisi löydettävä hyvä täytekakku?"
viesti_c = "Hei Alice."
viesti_d = "Entten tentten teelika mentten, hissun kissun vaapula vissun, eelin keelin plot, viipula vaapula vot, Eskon saum, pium paum, nyt mä lähden tästä pelistä pois. Puh pah pelistä pois!"

# Lasketaan neljän viestin tiivisteet
tiiviste_a = laske_tiiviste(viesti_a)
tiiviste_b = laske_tiiviste(viesti_b)
tiiviste_c = laske_tiiviste(viesti_c)
tiiviste_d = laske_tiiviste(viesti_d)

Lopuksi verrataan, montako bittiä viestin a ja b tiivisteet eroavat toisistaan.

Inactive
# Alustetaan funktiot ja tiivisteet valmiiksi
from xip import alusta_t700, alusta_t701, alusta_t702
laske_tiiviste, testaa_tiivisteet = alusta_t700()
viesti_a, viesti_b, viesti_c, viesti_d = alusta_t701()
tiiviste_a, tiiviste_b, tiiviste_c, tiiviste_d = alusta_t702()

# Näytetään viestit, ja niiden tiivisteet heksadesimaalisena
print("Viesti a:", viesti_a)
print("Viesti b:", viesti_b)
print("Viesti c:", viesti_c)
print("Viesti d:", viesti_d)

print("")

print("Tiiviste a:0x{}".format(tiiviste_a.hex()))
print("Tiiviste b:0x{}".format(tiiviste_b.hex()))
print("Tiiviste c:0x{}".format(tiiviste_c.hex()))
print("Tiiviste d:0x{}".format(tiiviste_d.hex()))

# Funktio laskee kuinka kuinka monella bitillä on eri arvot tiivisteissä
# Tähän alle joudut ehkä vaihtamaan eri tiivisteet
tiivisteiden_ero_bitteinä = testaa_tiivisteet(tiiviste_a, tiiviste_b)

print("\nTiivisteet eroavat:",str(tiivisteiden_ero_bitteinä),"bitillä.")

Kun olet ajanut kaikki edelliset koodisolut, vastaa seuraaviin kysymyksiin. Joudut ehkä muuttamaan testatattavia tiivisteitä.

Tehtävän tekeminen edellyttää, että olet ajanut edelliset koodisolut ja katsonut mitä itse kirjoitetut funktiot tekevät. Joudut myös muokkaamaan koodia ja vertailemaan eri tiivisteitä.

Mitkä väittämät pitävät paikkansa? Valitse kaikkia oikeat vaihtoehdot.

Mitkä väittämät pitävät paikkansa? Tätä kysymystä varten joudut vaihtamaan ylemmän koodisolun viestejä ja tulostamaan laskettuja tiivisteitä.

Huimaa eikö totta? Kaikki tiivisteet eroavat yli sadalla bitillä toisistaan.


Alice ja Bob käyttävät tiivistettä

Kaikelle digitaalisessa muodossa olevalle datalle voi laskea tiivisteen.

Bob on aiemmin salannut syntymäpäiväkakun kuvan. Salaus suoritettiin symmetrisellä AES-salaimella.

Seuraavaksi Bob laskee Alicelle tiivisteen selväkielisestä kuvasta. Tällöin Alice pystyy tarkistamaan, onko kuva todella se, jonka tiivisteen Bob on välittänyt hänelle.

Seuraavassa koodisoluissa Bob laskee tiivisteen kuvalle.

Inactive
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import hashlib

# Kerrotaan missä kuvatiedosto on
kuvatiedosto = './images/cake.png'

# Näytetään kuva josta lasketaan tiiviste
plt.imshow(mpimg.imread(kuvatiedosto))
plt.show()

# Lasketaan tiiviste
with open(kuvatiedosto,'rb') as tiedosto:
    tavut = tiedosto.read()
    hex_tiiviste = hashlib.sha256(tavut).hexdigest()
    print("Kuvan tiiviste on", hex_tiiviste)

Seuraavaksi Bob suorittaa salauksen.

Inactive
import hashlib
import os

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

# Tehdään avain
suuri_luku = b"S\xef8\x8e\xc8\xddff\xf4\x81\xc7 '2\x97\x88\x86\t3\x12\xfbTI5FQ\x05$P\xe85o\\a\xae\x80i=U\x16\t3\xc0i\xbd\xee\xcd\xc0\\\xf6\x13\x0f\xe8jK\x0e\xd9\xf8\xab\xbc\r!0d?\nv\xad\x06\x83\x01,\x83\xf9%\xa8n4\x12\xb3gU\x12\xda\xecw!\xf2O\xb5\r\x1e\xdd\xf0\xa2H5\xe9\xf2\x81\xd4B\x91\xf7r\xfaI!d\xf7\xfa\xafW\xce\xb2\x00x]\xb2[\xf5@:\x07\x9d\x9db\xca\xa8?Ue3G\xe1YVh\x80\x051g\x00\x07hQ\xc4\xf3K\x1b\x0f\\\x08\xfe\x19&6\xb2\x14\xcb\x90\xfd\xfdq+\xd6X\xa5\xc6Q\x84`U\x13\xee#Y\xb2\xdf8\x7fJ8l\x8bV\x89>\xe9\xc7\x83/\xdf\xf7(\x8b\xc0\xb3\xf8\xe11\x04\xcb\x99\x96[\xbdN\x8d\x8d-\xaa\x03\x90*m\xc3\xa1\x08\x91\x17\xd96^9#iX\xfc\xef \xdd\xd1\xcd\xff\xedY\x98\xbaA\xf2g\xbf\xb7q\xb4^\xa5\xc8\xd4\xcb\xd9\x0eZb\xf8"
avain = suuri_luku[:16]

# Tehdään IV
IV = os.urandom(16)

# Luodaan AES-CBC lohkosalain käyttäen 128-bittistä avainta.
aes_cbc_encrypt = Cipher(algorithms.AES(avain), modes.CBC(IV)).encryptor()
# Luodaan päddäyksen tuottaja
päddää = padding.PKCS7(128).padder()

# Kerrotaan missä salattava tiedosto on
kuvatiedosto = './images/cake.png'

# Tähän luetaan tiedosto, se voi olla mikä tahansa tiedosto
tavut = bytes()

# Ladataan tiedosto
with open(kuvatiedosto,'rb') as tiedosto:
    tavut = tiedosto.read()

# Lasketaan tiiviste
tiiviste = hashlib.sha256(tavut).hexdigest()

# Pädätään tiedosto
pädätty_tiedosto = päddää.update(tavut)+päddää.finalize()

# Salataan tiedosto
salattu_tiedosto = aes_cbc_encrypt.update(pädätty_tiedosto)+aes_cbc_encrypt.finalize()

print("Bob on salannut tiedon ja lähettää Alicelle tarvittavat tiedot.")

Mitä tietoa Alicen ja Bobin pitää jakaa?

  • Bob ja Alice ovat jo aiemmin jakaneet avaimen, joka on heidän yhteinen salaisuutensa. Heillä ei ole vielä keinoja jakaa avainta tai muodostaa jaettua salaisuutta internetin yli, joten Alicen ja Bobin on täytynyt tavata kasvotusten jakaakseen avaimen.

  • Bob lähettää salatun tiedoston sekä kertakäyttönumeron IV sähköpostilla Alicelle.

  • Tiivisteen hän puolestaan välittää Alicelle suojatulla chat-ohjelmalla.

Seuraava koodisolu suorittaa seuraavat toiminnot:

  • Alice purkaa salatun tiedoston.

  • Alice tarkistaa, että puretun tiedoston tiiviste täsmää Bobin lähettämään tiivisteesen.

Inactive
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import hashlib
import numpy as np

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

from xip import alusta_t703

avain, IV, salattu_tiedosto, saatu_tiiviste = alusta_t703()

# Luodaan AES-CBC lohkosalain käyttäen 128-bittistä avainta.
aes_cbc_dekoodaus = Cipher(algorithms.AES(avain), modes.CBC(IV)).decryptor()
# Luodaan päddäyksen tuottaja
unpäddää = padding.PKCS7(128).unpadder()

purettu_data = aes_cbc_dekoodaus.update(salattu_tiedosto)+aes_cbc_dekoodaus.finalize()
tallennettava_data = unpäddää.update(purettu_data)+unpäddää.finalize()

# Tallennetaan purettu tiedosto, oletetaan sen olevan png kuvatiedosto
purettu_tiedosto = "olisiko_kakku.png"
with open(purettu_tiedosto,'wb') as tiedosto:
    tiedosto.write(tallennettava_data)

tarkistus = bytes()

# Luetaan purkamamme informaatio takaisin tiedostosta
with open(purettu_tiedosto, 'rb') as tarkistatiedosto:
    tarkistus = tarkistatiedosto.read()

# Lasketaan itse tiivste puretusta tiedostosta
laskemani_hex_tiiviste = hashlib.sha256(tarkistus).hexdigest()

if saatu_tiiviste == laskemani_hex_tiiviste:
    print("Hienoa, tiivisteet täsmäävät! Tiedostoa ei ole muutettu.")
    # Näytetään purettu kuva josta
    plt.imshow(mpimg.imread(purettu_tiedosto))
    plt.show()

else:
    print("Epäilyttävää, olisikohan joku käpälöinyt kuvaa?")
    print("Laskemani tiiviste:", laskemani_hex_tiiviste)
    print("Saamani tiiviste  :", hex_tiiviste)

Yhteenveto

Tähän mennessä olemme oppineet seuraavia asioita:

  • Opimme jo aiemmissa moduuleissa salaamaan minkä tahansa tiedoston symmetrisillä salaimilla.

  • Ymmärrämme, että symmetristen salainten avainta ei pidä jakaa muutoin kuin henkilökohtaisesti (ainakaan ennen kuin opimme julkisen avaimen menetelmät).

  • Osaamme laskea ja varmistaa tiivisteen kaikille tietokoneessa esiintyville tiedostoille.

  • Ymmärrämme, että tiiviste vahvistaa informaation olevan eheää.

  • Ymmärrämme myös sen, ettei tiiviste estä informaation muuttamista.

Seuraavaksi perehdymme matematiikkaan, johon tiivisteiden toiminta perustuu.

Palautusta lähetetään...