XOR titkosítás

Használható segédanyagok: Haskell könyvtárainak dokumentációja, (lokális!) Hoogle, a tárgy honlapja és a BE-AD rendszerbe feltöltött beadandók. Ha bármilyen kérdés, észrevétel felmerül, azt a felügyelőknek kell jelezni, nem a diáktársaknak!

A feladatok egymásra épülnek ezért érdemes ezeket a megadásuk sorrendjében megoldani, de legalább megérteni az aktuális feladatot megelőző feladatokat! A függvények definíciójában lehet, sőt javasolt is alkalmazni a korábban definiált függvényeket (függetlenül attól, hogy sikerült-e azokat megadni).

Tekintve, hogy a tesztesetek, bár odafigyelés mellett íródnak, nem fedik le minden esetben a függvény teljes működését, határozottan javasolt még külön próbálgatni a megoldásokat beadás előtt, vagy megkérdezni a felügyelőket!

Részpontszámokat csak az elégséges szint elérése után lehet kapni!

A feladat összefoglaló leírása

A feladatban a XOR (kizáró vagy) titkosítással foglalkozunk, amely egy egyszerű titkosítási eljárás. A szöveget egy kulcs segítségével titkosíthatjuk és ugyanezzel a művelettel tudjuk visszafejteni a kulcs ismeretében. Az eljárást gyakran használják más, összetettebb titkosítási eljárásokban.

Az eljárás lényege, hogy a titkosítandó szöveg mellé megadunk egy kulcsot (egy tetszőleges szöveg) és ennek segítségével végezzük a titkosítást. Azaz, párosával vesszük a két szöveg elemeit, és ezekre alkalmazzuk a bitenkénti kizáró vagy (xor) műveletet.

Két karakter (Char) típusú érték esetén a kizáró vagy műveletet (xor) az alábbiak szerint kell érteni:

  1. meghatározzuk mindkét érték karaktertábla-beli értékét (decimális érték),

  2. ezeket az értékeket átalakítjuk bináris reprezentációra,

  3. elvégezzünk a bitenkénti xor műveletet,

  4. ezt visszaalakítjuk decimális értékre,

  5. megadjuk az értékhez tartozó karaktert.

Például: Ha a titkosításhoz megadott kulcs a “day” és a “Good work!” szöveget kell titkosítanunk, akkor a kulcs ismétlésének segítségével fogjuk ezt megtenni (“daydaydayd”, mivel rövidebb mint a titkosítandó szöveg) és a titkosított szöveg a "#\SO\SYN\NULA\SO\v\DC3\DC2E" lesz. Azaz

x y x xor y
'G' 'd' '#'
'o' 'a' '\SO'
'o' 'y' '\SYN'
'd' 'd' '\NUL'
' ' 'a' 'A'
'w' 'y' '\SO'
'o' 'd' '\v'
'r' 'a' '\DC3'
'k' 'y' '\DC2'
'!' 'd' 'E'

Ezzel az eljárással egy feltörhetetlen kódolást kaphatunk, ha az alábbi szabályokat betartjuk:

A kódolt szöveg azonban könnyen feltörhető (pl. az elemek gyakoriságának elemzésével), ha:

A kódolt szöveg akkor is visszafejthető ügyeskedéssel, ha az eredeti szöveg egyes részletei ismertek (known-plaintext attack).

Például: A második világháború során több titkosított üzenetet fejtettek vissza, mert nem figyeltek oda a kulcs teljes véletlenszerűségére, ugyanazt a kulcsot használták, vagy kiderült az eredeti szöveg egy részlete.

A feladatban szövegek titkosításával, illetve azok visszafejtésével foglalkozunk az eredeti szöveg részleteinek ismerete segítségével.

A feladatban használjuk a BitString típusszinonimát, amely egy bináris (0 és 1 értékekből álló) számrendszerbeli számot ábrázol. A biteket tartalmazó listában helyiérték szerint növekvő sorrendben adottak a számjegyek.

type BitString = [Int]

Tehát, egy [0,1,1] lista a neki megfelelő 0 * 20 + 1 * 21 + 1 * 22 kifejezéssel adható meg, amely értéke 6.

Egész szám bitsorozattá alakítása (1 pont)

Adjuk meg a decimális számot bináris számmá alakító műveletet! Ne feledjünk, hogy a lista elején a legkisebb helyiértékű számjegy található!

Feltesszük, hogy a paraméter nemnegatív, ezt nem kell ellenőrizni.

toBitString :: Int -> BitString

Test>
[] :: BitString
Test>
[0, 1] :: BitString
Test>
[0, 1, 0, 1] :: BitString
Test>
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1] :: BitString

Karakter bitsorozattá alakítása (2 pont)

Adjuk meg azt a műveletet, amely előállítja egy karakter bináris reprezentációját! A karakter bináris értékét a karakterkódból kell előállítani az előző művelet (toBitString) felhasználásával.

A feladatnál feltehetjük, hogy az adott karakter 7 hosszúságú bit sorozattal megadható, tehát a függvénynek 7 hosszúságú bitsorozatot kell visszaadnia. Amennyiben az értékből előállított bináris sorozat ennél rövidebb lenne, akkor egészítsük ki 0 értékekkel a sorozatot a vége felől (nagyobb helyiértékek).

Segítség: a karakterkóddá alakításhoz használhatjuk a ord függvényt a Data.Char modulból.

charToBitString :: Char -> BitString

Test>
[0, 0, 0, 0, 1, 1, 0] :: BitString
Test>
[1, 0, 0, 0, 0, 1, 1] :: BitString
Test>
[0, 0, 0, 1, 0, 0, 1] :: BitString
Test>
[0, 1, 0, 1, 0, 0, 0] :: BitString

Bitsorozat karakterré alakítása (2 pont)

Adjuk meg azt a függvényt, amely egy bitsorozatból előállítja a neki megfelelő Char típusú értéket! A bitsorozatot decimális értékké (karakterkóddá) alakítva könnyen meghatározható a hozzá tartozó karakter.

Segítség: a karakterkódból karakterré alakításhoz használhatjuk a chr függvényt a Data.Char modulból.

bitStringToChar :: BitString -> Char

Test>
'Q' :: Char
Test>
'1' :: Char
Test>
'\SOH' :: Char
Test>
'\NUL' :: Char

Kizáró vagy művelet bitsorozatokra (1 pont)

Definiáljuk a bitsorozatokon értelmezett kizáró vagy műveletet!

Az egyes biteket tekintve a xor műveletet szokták modulo 2 összeadásnak nevezni, így a biteken a következőképpen értelmezendő:

XORCipher61ea82b92bf82fe616c5deae65b5266f.png

Ezt kell megismételni az összes bitre a két bitsorozatban. A két bitsorozat nem feltétlenül azonos hosszúságú. Ha az egyik bitsorozat elfogyott, akkor a hosszabb bitsorozat maradék bitjeit változatlanul tesszük a végeredménybe (mintha a rövidebb sorozatot nullákkal lenne kipótolva).

xor :: BitString -> BitString -> BitString

Test>
[1, 1, 0, 1, 0, 1] :: BitString
Test>
[1, 1, 0, 1, 0, 1] :: BitString
Test>
[0, 1, 1, 1, 0, 1] :: BitString
Test>
[0, 1, 1] :: BitString

Tikosítás művelete (3 pont)

Megadtuk a szükséges műveleteket, amelyek a szöveg titkosításához szükségesek.

A könnyebb olvashatóság kedvéért bevezetünk két új típusszinonimát, a Key típus a titkosításhoz felhasználandó kulcsot, a Cipher típus pedig a titkosított szöveget jelöli.

type Cipher = String
type Key = String

Definiáljuk a titkosítást elvégző műveletet!

A műveletet a következő lépésekkel adhatjuk meg:

Fontos: A művelet megadásánál nem használható a következőekben adott decrypt művelet!

encrypt :: String -> Key -> Cipher

Test>
"\NUL.%6o\FS&=s\NUL &?<r" :: Cipher
Test>
"7\SOH\NAKH\DC4\a\n\n\ESCH\a\NUL\f\RS\RSH\ETX\GS\ESCI\SUB\GS\b\STX\DLEI\US\RS\NUL\NULC\GS\CAN\rE\RS\STX\DC3\tH\SOH\GS\EOT" :: Cipher

A kódolás szimmetrikus, azaz ugyanazzal a kulccsal kapjuk vissza az eredeti szöveget, mellyel titkosítottuk azt. A kizáró vagy tulajdonságainak köszönhetően a visszafejtés menete ugyanaz, mint a titkosításé, így a decrypt az előbb megadott encrypt művelettel könnyedén definiálható:

decrypt :: Cipher -> Key -> String
decrypt = encrypt

Ciklikusság vizsgálata (2 pont)

Adott két sorozat, legyen ez a és b. Az a és a b sorozat hossza legyen la és lb. Azt mondjuk, hogy a b sorozat az a sorozat ciklikus ismétlése, ha az alábbi feltételek teljesülnek:

Definiáljuk a feltételeknek megfelelő függvényt! Amennyiben a üres, adjunk hibaüzenetet az error függvénnyel (lásd példák)!

isCycledIn :: Eq a => [a] -> [a] -> Bool

Test>
False :: Bool
Test>
True :: Bool
Test>
False :: Bool
Test>
True :: Bool
Test>
True :: Bool
Test>
False :: Bool
Test>
⊥₁ :: Bool
⊥₁: isCycledIn: empty list
CallStack (from HasCallStack):
  error, called at ./XORCipher.lhs:274:21 in main:XORCipher

A tikosító kulcs meghatározása (2 pont)

Tegyük fel, hogy egy titkosított üzenet mellett rendelkezésünkre áll az eredeti kódolatlan üzenet is. A kódolás sajátossága, hogy a kódolt (Cipher) és az eredeti szöveg segítségével meghatározható a kódoláshoz felhasznált kód (Key) sorozat.

Határozzuk meg a kódoláshoz használt kulcsot!

Az alábbiak szerint járhatunk el:

getKey :: Cipher -> String -> Key

Test>
"pear" :: Key
Test>
"fox" :: Key

Üzenet megfejtése (2 pont)

Tegyük fel, hogy rendelkezésre áll az eredeti szöveg egy részlete, és ezen részleges információ segítségével kell megfejtenünk a kódolt szöveget.

Egy üzenetről csak annyit tudunk, hogy milyen témájú (az egyszerűség kedvéért tegyük fel, hogy ezzel az adott kifejezéssel kezdődik az eredeti üzenet), de a teljes tartalmát nekünk kell kiderítenünk.

Adjuk meg azt a függvényt, amely segítségével dekódolható az üzenet teljes tartalma!

A rendelkezésre álló információk:

Az alábbi lépéseket kell elvégeznünk:

Megjegyzés: feltehetjük, hogy a kódoláshoz egyszerű kulcsokat használtak, amelyek nem hosszabbak az ismert szöveg hosszánál, így a kulcs egyértelműen kitalálható.

decodeMessage :: Cipher -> String -> String

Test>
"Weather: it will rain today" :: String
Test>
"Supplies: We are out of ammunition!" :: String
Test>
"Central Intelligence: our spies have been deployed!" :: String

Pontozás (elmélet + gyakorlat)