Jelszavak

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!

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ározattan 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

Ebben a feladatban egy jelszavakat ábrázoló típust és annak néhány műveletét kell megvalósítanunk. Az implementáció során alkalmazott egyes ötleteket a feladatokon keresztül mutatjuk be.

Szöveg leképezése egész számmá (2 pont)

A jelszavakat általában szöveges formában kapjuk meg. Biztonsági okokból azonban nem közvetlenül ezt az alakját használjuk fel az ellenőrzés során, hanem ebből képezünk egy egész számot egy megfelelő függvény segítségével.

Ehhez először bevezetjük a Hash szinonímát, amely ilyen egész számokat jelöl.

type Hash = Integer

A leképezéshez használt függvényünk képlete legyen a következő:

Password41681fadec73dd40f40547d666fc82f3.png

Tehát vegyük a karakterlánc egyes karaktereinek kódját (a Data.Char.ord függvénnyel), emeljük a 2-t ennek megfelelő hatványokra, majd ezt sorozzuk össze poziciónak megfelelő pozitív egész számmal, végül adjuk össze az egyes karakterekhez az előbbiek szerint kiszámolt részeredményeket.

hash :: String -> Hash

Test>
0 :: Hash
Test>
12985812748737981988932227635871744 :: Hash
Test>
99668775071280816112222475981464535040 :: Hash

Óvatosnak kell lennünk viszont akkor, amikor több, ugyanazzal a módszerrel leképzett jelszót tárolunk egyazon állományban. Ha ugyanis ezt eltulajdonítják, akkor ugyanazt a módszert próbálgatva könnyedén vissza tudják fejteni a jelszavainkat. Ezért találták ki, hogy a kódolási eljárás során valamilyen további függvénnyel normalizálják (általában erősítik) a felhasználótól kapott jelszót.

Ehhez most mi is bevezetünk további két szinonímát: a Salt típust, amely a jelszóhoz hozzáadandó toldalékot ábrázolja, valamint a KDF (mint key derivation function) típust, amely leírja, hogy milyen módon transzformáljuk tovább a jelszót.

type Salt = String
type KDF  = Salt -> String -> String

Kulcsleképző függvény I. (1 pont)

Feladatunk most egy egyszerű kulcsleképezés elkészítése. Ebben az esetben a kulcsot úgy képezzük a kapott karakterláncból, hogy összefűzzük a toldalékkal, majd a saját megfordítottjával.

kdf1 :: KDF

Test>
"fooS4L777oof" :: String
Test>
"passworddrowssap" :: String

Kulcsleképző függvény II. (1 pont)

Készítsünk egy másik kulcsleképző függvényt: ebben az esetben fogjuk a karakterláncot és utánamásoljuk a toldalékot addig, amíg a megadott hosszt nem el értük. (Természetesen, ha a jelszó eleve hosszabb ennél, akkor ehelyett abból fogunk levágni.) Ha a toldalék üres, akkor magát a karakterláncot ismételgetjük a kívánt hossz eléréséig.

kdf2 :: Int -> KDF

Test>
"passwordpasswordpasswordpassword" :: String
Test>
"apasswor" :: String
Test>
"fooabcdabcdabcdabcdabcdab" :: String

Ezután már meg tudjuk adni a jelszavakat ábrázoló típust.

data Password = P Hash Salt
  deriving (Show,Data,Typeable)

Ebben tehát látható, hogy egy jelszót a belőle képzett egész számmal és a hozzá tartozó toldalékkal írunk le.

Jelszavak képzése (2 pont)

Az előbbieknek megfelelően tehát egy jelszót egy kulcsleképezés, egy toldalék és egy karakterlánc birtokában tudunk előállítani. Először a toldalék felhasználásával a karakterláncból képezzünk kulcsot, majd számítsuk ki a neki megfelelő egész számot!

mkPassword :: KDF -> Salt -> String -> Password

Test>
P 882732140022507197709476244116996096 "Sa77t" :: Password
Test>
P 8322950956743630369429806361109069824 "Sa77t" :: Password
Test>
P 91068695580102028587879960541663330304 "LongSalt" :: Password

Jelszavak ellenőrzése (3 pont)

A jelszavak ellenőrzése ehhez nagyon hasonló. Megkapjuk a jelszó (fentiek szerint) kódolt alakját, a rendelkezésre álló kulcsleképező függvényeket és az ellenőrizendő jelszót. A kulcsleképzéseket próbálgatva nézzük meg, hogy valamelyikkel kapunk-e olyan jelszót, amely egyező egész számot generál!

Láthatjuk, hogy valójában jelszavanként változhat (a toldalékkal együtt) a leképezés módja is. Viszont ezt nem tároltuk el a jelszóval, ezért kell végigpróbálni. Célunk ezzel is nehezíteni a visszafejtést.

checkPassword :: Password -> [KDF] -> String -> Bool

Test>
True :: Bool
Test>
False :: Bool
Test>
False :: Bool

A következő lépésben szeretnénk a jelszavainkat szöveges állományokban tárolhatóvá tenni. Ehhez először tudnunk kellene ezeket karakterláncokká alakítani.

Ehhez bevezetjük az Alphabet típust, amely a kódoláshoz használt ábécét fogja ábrázolni (mint karakterek sorozata).

type Alphabet  = [Char]

Egy ilyen minta ábécé lehet például az alábbi:

alphabet :: Alphabet
alphabet = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z']

Egész számok kódolása karaktersorozattá (3 pont)

A jelszavak szöveges ábrázolásának alapját egy olyan függvény képezi, amely a jelszóhoz kiszámolt Hash értéket tudja szövegre alakítani a megadott ábécé szerint. Itt lényegében egy olyan átváltásra kell gondolnunk, ahol a számrendszert az ábécé hossza adja meg, és ennek számjegyei az ábécében szereplő szimbólumok.

Megjegyzés: A könnyebb implementáció érdekében az így kiszámolt számjegyek kövessék egymást fordított sorrendben!

numToWord :: Alphabet -> Integer -> String

Test>
"HHZVeTXq79" :: String
Test>
"f7w" :: String
Test>
"754321" :: String
Test>
"FFFF" :: String

A jelszavak teljes szöveges alakjához még az hiányzik, hogy a fenti módszerrel előállított karaktersorozathoz hozzá tudjuk tenni a kódolás során alkalmazott toldalékot.

Ehhez bevezetjük az elválasztójelet ábrázoló Separator típust:

type Separator = Char

amelynek lehet például a dollárjel:

separator :: Char
separator = '$'

Jelszavak szöveges alakra hozása (2 pont)

A jelszavakból szöveget tehát úgy tudunk készíteni, ha az adott ábécé szerint átalakítjuk a jelszót ábrázoló egész számot, majd ehhez az elválasztójellel hozzáfűzzük a kulcsképzésnél alkalmazott toldalékot.

renderPassword :: Alphabet -> Separator -> Password -> String

Test>
"ixS3XOnWKClP4oyR6qLm7o$SALTy" :: String
Test>
"Ym4YGXmRc3Ny5MUXFCP7A1:!!!!" :: String
Test>
"UQEotAYCgJlD8QYptNc6Wg4?" :: String

Karaktersorozatok átalakítása egész számmá (4 pont)

A szöveges változat létrehozásán kívül még fontos, hogy be is tudjuk olvasni szövegből jelszavakat, adott ábécé szerinti kódolással. Ebben a függvényben a jelszót ábrázoló egész szám kódolt alakját kell tudnunk visszaolvasni. Arra viszont figyeljük, hogy nem minden esetben található meg a visszakódolandó szöveg összes karaktere az ábécénkben. Ezért ez utóbbi esetben ne adjon a függvény vissza eredményt!

wordToNum :: Alphabet -> String -> Maybe Integer

Test>
Just 1202416604951 :: Maybe Integer
Test>
Just 510 :: Maybe Integer
Test>
Just 3210 :: Maybe Integer
Test>
Nothing :: Maybe Integer

Jelszavak beolvasása szövegből (3 pont)

Végül készítsük el azt a függvényt, amellyel a teljes jelszót be tudjuk olvasni! Ehhez először a megadott elválasztójel mentén szét kell bontanunk a szöveget. Amennyiben ez nem lehetséges, akkor a függvény ne adjon vissza eredményt! Ha sikerült, akkor az előbbiek szerint kódoljuk vissza a Hash értéket és a toldalékkal együtt képezzünk belőle jelszót. Ha a visszakódolás nem sikerült (nem ad eredményt), akkor ez a függvény se adjon eredményt!

parsePassword :: Alphabet -> Separator -> String -> Maybe Password

Test>
Just (P 1162550178044360926538112711767493558387537750185 []) :: Maybe Password
Test>
Just (P 1053371667944745909617122243317451456512 "SALTy") :: Maybe Password
Test>
Just (P 7654321 "Ugo32") :: Maybe Password
Test>
Nothing :: Maybe Password
Test>
Nothing :: Maybe Password

Pontozás