Cryptopals set 1
Tässä raportissa tutustumme cryptopals - kryptografia haasteisiin. Tämä ensimmäinen sarja on kokonaisuudessaan tarkoitettu vain perusasioihin. Kuten Hex->Base64 käännökseen, XOR-kryptaukseen ja tämän yksinkertaiseen murtoon.
Ennen haastetta, minulle ei mitään käytännön kokemusta näistä.
Välissä kuitenkin lyhyesti opettajan sivuilta pythonin perus-käsittelyä. Itse käytän jääräpäisesti kuitenkin nanoa ja omia editoreita 😃 Raporttivaatiumksen kirjaa en saanut auki.
Aloitetaan!
Ensimmäinen haaste (hex->base64)
Hex ja base64 ovat fundamentaalisesti erilaiset. Niitä ei mitenkään erityisen helppo vaihtaa päittäin.
Sen sijaan, että säädämme tavuja näin on helpompi vain jakaa tulos raakadataan välissä. Voisin käyttää python-editoria tähän, mutta olen ennenkin vain käyttänyt xxdtä vastaaviin. Xxd ihan linuxin sisäänrakennettu hex-muunnin. Voin käyttää -r lipun kanssa, jolloin muunnetaan hex arvo takaisin raakadataksi.
Tämä sitten käännetään takaisin base64 arvoon. Putkilla koko setti helppo tehdä.
bash echo -n '49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d' | xxd -r -p | base64
Eli otetaan arvo, jonka jälkeen uusi rivi -n, ajetaan xxd läpi raaka-dataksi ja tämän jälkeen muunnetaan base64 muotoon.

Haaste kaksi (kiinteä XOR arvo)
Tässä meillä data-arvo, sekä XOR-arvo jotka voidaan yhdistää avatuksi setiksi.
Tehdään python setti. Ensin arvot. Oletin, että kyseessä nyt hex arvot.
hex_sis = "1c0111001f010100061a024b53535009181c"
hex_avain = "686974207468652062756c6c277320657965"
Jotka raakatavuiksi (bytes)
bit_sis = bytes.fromhex(hex_sis)
bit_avain = bytes.fromhex(hex_avain)
Itse funktio (^ pythonissa XOR). Käytän tässä zip funktiota yhdistämään arvot jonoon.
tulos_bit = bytes([tavu_sis ^ tavu_avain for tavu_sis, tavu_av in zip(bit_sis, bit_av)])
Ja tämä lopputulos tavut->hex ja printataan tulokset. Lisään myös dekoodatut versiot alkuperäisistä arvoista/teksteistä jos saadaan siihen jotain järkeä?
tulos_hex = tulos_bit.hex()
print("\nhex:")
print(tulos_hex)
print("\nalunperin hex_sis:")
print(bit_sis.decode())
print("\nalunperin hex avain:")
print(bit_avain.decode())
print("\ndekoodattu")
print(tulos_bit.decode())

Haaste kolme (yhden tavun XOR salaus)
Eli tässä meidän täytyy yrittää ratkaista tehtävä tietämättä XOR avainta. Selvittelen ympäriinsä internetistä ja kyselen 27b mistralilta ehdotusta.
Mistral: Tämän tyylinen salaus on paras yrittää avata pisteytysfunktiolla...
Meidän täytyy siis luoda arvojärjestelmä jossa kokeiltuja arvauksia verrataan - tässä ei kuitenkaan kuin 256 mahdollista vaihtoehtoa, kun yksi tavu avaimena. Tehdään siis niin! Brute force! Ja lähden muutamalla oletuksella, esim. että vastaus on englannin kielellä.
Teen pitkällä kaavalla seuraavan koodin pisteyttämään sanakirjan. Käytän apuna Mistrallia, mutta lähinnä apuna, että saan pikaisesti tehtyä.
# collections moduuli ja defaultdict luokka. Sanakirjasettiä.
from collections import defaultdict
# Funktio pisteet_tekstille. Mistral ehdottaa, yleisyyden yleisimpiin englantilaisiin kirjaimiin (etaoinshrdlucmfwypvbgkjqxz).
def pisteet_tekstille(teksti):
"""piste-teksti"""
yleisyys = " etaoinshrdlucmfwypvbgkjqxz"
return sum(merkki in yleisyys for merkki in teksti.lower())
salattu_hex = "1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736"
salattu_tavut = bytes.fromhex(salattu_hex)
tulokset = defaultdict(list)
#XOR avaimet käydään läpi 256.
for avain in range(256):
purettu_tavut = bytes([tavu ^ avain for tavu in salattu_tavut])
purettu_teksti = purettu_tavut.decode('ascii', errors='ignore')
pisteet = pisteet_tekstille(purettu_teksti)
tulokset[pisteet].append((avain, purettu_teksti))
#Printataan vastaukset ja pisteet.
parhaat_pisteet = max(tulokset.keys())
parhaat_tulokset = tulokset[parhaat_pisteet]
print(f"Paras tulos: {parhaat_tulokset} pisteillä {parhaat_pisteet}")

Neljäs tehtävä (tiedostosta XOR)

Eli nyt pitää yrittää löytää kaikista hex riveistä yksi xorattu avain. Loput on satunaista dataa.
Ohjeena voidaan siis käyttää edellistä pohjana ja lähteä vähän laajentamaan. Noise!
Muutan koodin käymään rivejä, annan perusarvot ja samalla toimintalogiikalla kuin edellinen. Teen myös perus tsekkaukset, että löytyy rivejä tai tiedosto mitä katsotaan (4txt).
Kokoan koodin mistralilla. Itse teen funktion arvot, mutta mistral auttaa rakentamaan struktuurin. Onneksi python itselle suhteellisen tuttu setti.
Monen tunnin ja tuskan takana seuraavassa koodi ja kommentit. Pohja-ajatus on sama kuin edellisessä.
def etsi_paras_tulos_riville(salattu_hex_rivi):
"""
Rivi kerrallaan tsek ja palautus
"""
try:
salattu_tavut = bytes.fromhex(salattu_hex_rivi)
except ValueError:
# Ohitetaan rivit, jotka eivät ole validia heksaa
return (-1, None, None)
yleisyys = " etaoinshrdlucmfwypvbgkjqxz"
paras_pisteet = -1
paras_tulos = (-1, None, None)
for avain in range(256):
purettu_tavut = bytes([tavu ^ avain for tavu in salattu_tavut])
purettu_teksti = purettu_tavut.decode('ascii', errors='ignore')
pisteet = sum(merkki in yleisyys for merkki in purettu_teksti.lower())
if pisteet > paras_pisteet:
paras_pisteet = pisteet
# Tallennetaan myös alkuperäinen rivi ja rivinumero debuggausta varten
paras_tulos = (pisteet, avain, purettu_teksti)
return paras_tulos
def main():
kaikki_ehdokkaat = []
# Varmista, että 4.txt on samassa kansiossa
try:
with open('4.txt', 'r') as tiedosto:
for rivi in tiedosto:
hex_rivi = rivi.strip()
if not hex_rivi: continue
ehdokas = etsi_paras_tulos_riville(hex_rivi)
kaikki_ehdokkaat.append(ehdokas)
except FileNotFoundError:
print("ei löydy tiedostoa.")
return
if not kaikki_ehdokkaat:
print("ei löydy rivejä")
return
voittaja = max(kaikki_ehdokkaat, key=lambda item: item[0])
voittajan_pisteet, voittajan_avain, voittajan_viesti = voittaja
print(f"Kaikkien rivien paras ehdokas löydetty!")
print("-" * 40)
print(f"Pisteet: {voittajan_pisteet}")
print(f"Avain: {voittajan_avain} (merkki: '{chr(voittajan_avain)}')")
print(f"Viesti: {voittajan_viesti}")
# mainfunktio
if __name__ == "__main__":
main()

Vitostehtävä (XOR salaus avaimella)
Hehe. Nyt mennään vähän syvemmälle erämaahan. Eli selkotekstin tavut salataan käyttämällä toistuvaa avainta (ICE).
Kommentit koodin sisällä. Laitan myös debuggausta varten odotetun tuloksen niin koodia tehdessä voin kokeilla, että toimiiko.
Lyhyesti kuitenkin, ajan ohjelman osiin. Ensin XOR funktio, valmistelen datan, suoritan salauksen ja tulos hexasta tarkistukseen.
def xor_toist(teksti_tavu: bytes, avain_tavu: bytes) -> bytes:
"""
Alkaa - "ICE" tavusalauksen. List comprehension tekee saman kuin pidempi for-silmukka:
käy läpi selkotekstin tavut, valitsee oikean avaintavun,
suorittaa XOR-operaation ja kerää tulokset.
"""
avain_pituus = len(avain_tavu)
return bytes([
t_byte ^ avain_tavu[i % avain_pituus]
for i, t_byte in enumerate(teksti_tavu)
])
# --- Pääohjelma ---
# Valmisteltu data
teksti = """Burning 'em, if you ain't quick and nimble
I go crazy when I hear a cymbal"""
avain = "ICE"
# Setti tavuiksi
teksti_tavu = teksti.encode('ascii')
avain_tavu = avain.encode('ascii')
# Salausosuus
salattu_tulos = xor_toist(teksti_tavu, avain_tavu)
# hex ja tarkistus
tulos_hex = salattu_tulos.hex()
odotettu_hex = (
"0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272"
"a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f"
)
print("Tiedetty tulos:")
print(tulos_hex)
print("\nToimiiko? tulos:")
print(odotettu_hex)

Kutostehtävä ja eteenpäin (base64, toistuva XOR)
Tästä palaan vielä myöhemmin. Tämä ollut viikon kesken. Vaikea, mutta ei mitenkään mahdoton. Junnaan keysize normalisoinnin ymmärryksessä ja koodin saannissa toimimaan vielä.
Kommentit
Tykkään todella paljon näistä cryptopals tehtävistä. Antaa hyvän kuvan salauksen vaatimuksista ja lähinnä yksinkertaistenkin salausten purun vaikeudesta. Nämä jatkoon ja jatkan näitä eteenpäin.
Lähteet
xxd https://linux.die.net/man/1/xxd
Hakkereille pythonia. https://terokarvinen.com/python-for-hackers/
Dokumentissa käytetty apuna python binäärien struktuurissa LLM mistra-small3.2:24b mallia. Rautana käytössä ollut Macbook pro m2 max 64gb ja kääntäjänä LM-studio. https://huggingface.co/mistralai/Mistral-Small-3.2-24B-Instruct-2506 https://lmstudio.ai
Kuvat optimoitu https://optimage.app
Käytetty aika n. 10h