Problem Set #1: Top Secret
Ciele
- Osvojiť si základy dynamickej správy pamäte (alokácia a dealokácia).
- Naučiť sa pracovať s bitovými operátormi a operáciami v jazyku C.
Upozornenie
Toto zadanie je potrebné odovzdať do 10. mar. 2024
23:59:59. Na diskusiu používajte kanál na slack-u
#top-secret
.
Šifrovanie
Videli ste filmy National Treasure alebo National Treasure II.? Nicolas Cage v roli Benjamina F. Gatesa tu ako novodobý Indiana Jones lúšti rozličné logické hádanky, ktoré mu zanechali americkí predkovia pri ceste za pokladom. Niektoré z nich spočívali v rozlúštení zakódovaného odkazu, pre rozlúštenie ktorého musel Gates poznať šifru a kľúč pre jej rozlúštenie.
Šifrovanie zohralo v histórii už mnohokrát dôležitú úlohu. Či už to bol Cézar, ktorý nechával správy vojenského významu šifrovať jednoduchou substitučnou šifrou dnes známou práve ako Cézarova šifra, alebo nacistický šifrovací prístroj Enigma, rozlúštenie ktorého pomohlo ukončiť druhú svetovú vojnu odhadom o dva roky skôr.
V rámci tohto zadania bude vašou úlohou vyriešiť niekoľko úloh týkajúcich sa práve šifrovania textu v jazyku C. Implementujete dva moduly:
- Modul Playfair, v rámci ktorého implementujete Playfairovu šifru.
- Modul BMP, v rámci ktorého implementujete BMP šifru.
V rámci oboch modulov si samozrejme môžete vytvoriť aj ďalšie pomocné funkcie. Nesmiete však nijako meniť a upravovať hlavičkové súbory oboch modulov!
Modul Playfair
Tento modul je zameraný na šifrovanie a dešifrovanie textu
prostredníctvom Playfairovej
šifry, ktorú vo filme National
Treasure II. lúštil aj Nicolas Cage. Modul bude obsahovať
dve funkcie playfair_encrypt()
a
playfair_decrypt()
pre zašifrovanie a rozšifrovanie textu a
makro ALPHA
, ktoré bude obsahovať abecedu na šifrovanie s
vynechaným písmenom 'W'
.
Úloha #1: Playfairova šifra
Princípom Playfairovej šifry je rozdelenie textu do dvojíc znakov. Ak sú obidva znaky dvojice:
- na rovnakom riadku matice 5x5, nahradia sa písmenami napravo od nich
- v rovnakom stĺpci matice 5x5, nahradia sa písmenami pod nimi
- na rozdielnych riadkoch a stĺpcoch matice 5x5, nahradia sa písmenami na priesečníkoch daných stĺpcov a riadkov
Jedno písmeno z abecedy, ktoré sa málo vyskytuje, resp. nemení sémantiku slov jazyka, zameníme za iné (v našom prípade W za V). Túto zámenu realizujte pre kľúč pri šifrovaní a dešifrovaní, pre text iba pri šifrovaní.
K šifrovaniu je potrebné poznať kľúč, ktorého znaky sa nesmú opakovať
a nachádzajú sa na prvých miestach matice (ak sa opakujú tak vo vašej
implementácii zabezpečte, že duplikáty budú odstránené). V prípade, že
kľúč je "belfast"
, matica 5x5 je rozdelená nasledovne (v
našom prípade bol úmyselne vynechaný znak 'W'
):
B E L F A
S T C D G
H I J K M
N O P Q R
U V X Y Z
Ak s týmto kľúčom zašifrujeme slovo KRYPTOGRAFIA
,
výsledkom bude šifrovaný text MQXQIVMZBAME
, pre
MANAGER
to bude RGRBTAPZ
.
Vašou úlohou je vytvoriť funkcie
char* playfair_encrypt(const char* key, const char* text)
a
char* playfair_decrypt(const char* key, const char* text)
,
pomocu ktorých zašifrujete a odšifrujete text aplikovaním
Playfairovej šifry. Jednotlivé parametre funkcií sú
nasledovné:
const char* text
- Odkaz na reťazec, ktorý reprezentuje text na zašifrovanie. Tento text môže pozostávať len z písmen, pričom na veľkosti písmen nezáleží, alebo zo znaku medzery. Žiadne iné znaky nie sú prípustné (nie je možné ich zašifrovať).const char* key
- Odkaz na reťazec, ktorý reprezentuje kľúč, pomocou ktorého bude text zašifrovaný. Kľúč je reprezentovaný textom, v ktorom nezáleží na veľkosti písmen. Jednotlivé písmená sa v kľúči nemôžu opakovať (ak sa opakujú tak vo vašej implementácii zabezpečte, že duplikáty budú odstránené). Medzery v kľúči sú ignorované.
Funkcia vráti referenciu na zašifrovaný reťazec, ak bolo šifrovanie alebo dešifrovanie úspešné. Výsledný reťazec vypisujte v podobe dvojíc veľkých písmen oddelených medzerami.
Ak pred šifrovaním zistíte, že sú v texte dve po sebe idúce písmená
rovnaké, medzi tieto dve písmená pridajte písmeno 'X
‘.
Písmeno 'X
’ rovnako pridajte na koniec vstupného textu v
prípade, ak je jeho dĺžka nepárna (po pridaní 'X
’ medzi
dvojité znaky a odstránení medzier). Znak 'X
’ sa nepridáva
medzi 2 rovnaké znaky 'X
’ a šifruje sa podľa stĺpca
matice.
Pri dešifrovaní sa výskyt znaku 'W
’ pri vstupnom texte
považuje za chybu a funkcia na dešifrovanie má v takomto prípade vrátiť
NULL
.
V prípade, že niektoré z pravidiel týkajúce sa vstupných parametrov
bolo porušené alebo funkcia na vstupe dostane miesto ktoréhokoľvek
parametra hodnotu NULL
, funkcia hodnotu NULL
aj vráti.
Použitie funkcie na zašifrovanie/dešifrovaonie sú ilustrované na nasledujúcich príkladoch:
char *encrypted, *decrypted;
// even length of string
= playfair_encrypt("SeCReT", "Hello world");
encrypted ("%s", encrypted);
printf// "Hello world" --> "HELXLOVORLDX"
// IS JZ JQ XN TK JC
= playfair_decrypt("SeCReT", encrypted);
decrypted ("%s", decrypted);
printf// HELXLOVORLDX
(encrypted);
free(decrypted);
free
// odd length of string
= playfair_encrypt("world", "Hello");
encrypted ("%s", encrypted);
printf// "Hello" --> "HELXLO"
// JB RY DR
= playfair_decrypt("world", encrypted);
decrypted ("%s", decrypted);
printf// HELXLO
(encrypted);
free(decrypted);
free
// letter 'X' in message
= playfair_encrypt("Password", "Taxi please");
encrypted ("%s", encrypted);
printf// "Taxi please" --> "TAXIPLEASE"
// UP YH AK DO OB
= playfair_decrypt("Password", encrypted);
decrypted ("%s", decrypted);
printf// TAXIPLEASE
(encrypted);
free(decrypted);
free
// multi 'X's in message
= playfair_encrypt("please", "Taxxxiii");
encrypted ("%s", encrypted);
printf// "Taxxxiii" --> "TAXXXIIXIX"
// RS EE VJ JV JV
= playfair_decrypt("please", encrypted);
decrypted ("%s", decrypted);
printf// TAXXXIIXIX
(encrypted);
free(decrypted); free
Modul BMP
Keďže nám na bezpečnosti našich riešení záleží, v našom tíme kladieme bezpečnosť na druhé miesto (hneď po odstránení každej ryže, ktorú zistíme). A keďže všetky súčasné krypto algoritmy považujeme za nedostatočné (Enigma šuvix) a v tíme prevláda názor, že čo je jednoduché, to je geniálne, zvolili sme prístup kombinácie a variácie viacerých jednoduchých prístupov súčasne. A keďže ľudová múdrosť hovorí, že viac čiar - viac adidas, môžeme povedať rovnako, že úroveň bezpečnosti sa zvyšuje použitím viacerých jednoduchých (rozumej - geniálnych) postupov.
Šifra BMP nesie názov zložený z prvých písmen priezvisk trojice zakladateľov nášho kryptografického tímu (Biňas-Madeja-Pietriková). Výhodou oproti Playfairovej šifre je fakt, že pomocou BMP je možné zašifrovať ľubovoľný text (teda - až na niekoľko výnimiek spomenutých nižšie).
Úloha #2: Reverz
Vašou úlohou je vytvoriť funkciu
char* reverse(const char* text)
, vráti kópiu vstupného
reťazca v obrátenom poradí (UPPERCASE).
V prípade, že funkcia miesto reťazca dostane hodnotu
NULL
, funkcia túto hodnotu NULL
aj vráti.
char* reversed = reverse("Hello world!");
("%s\n", reversed);
printf// "!DLROW OLLEH"
Úloha #3: Vigenèrova šifra
Vigenèrova šifra predstavuje rozšírenie cézarovej šifry o kľúč, ktorý bude pri šifrovaní a dešifrovaní použitý.
Vašou úlohou je vytvoriť funkciu
char* vigenere_encrypt(const char* key, const char* text)
na zašifrovanie a funkciu
char* vigenere_decrypt(const char* key, const char* text)
na dešifrovanie textu pomocou Vénierovej šifry. Výsledný text
reprezentujte veľkými písmenami (UPPERCASE)
Význam jednotlivých parametrov funkcií je nasledovný:
key
- Reťazec reprezentujúci kľúč použitý na zašifrovanie aj odšifrovanie textu. Kľúč je reprezentovaný ako jedno slovo a môže pozostávať len zo znakov abecedy, pričom na veľkosti písmen nezáleží.text
- Reťazec na zašifrovanie resp. odšifrovanie.
Funkcia vráti adresu kópie reťazca zašifrovanú resp. odšifrovanú
pomocou Venierovej šifry alebo NULL
, ak
zašifrovanie resp. odšifrovanie nebolo úspešné.
char* encrypted;
// basic test with long text
= vigenere_encrypt("CoMPuTeR", "Hello world!");
encrypted ("%s\n", encrypted);
printf// "JSXAI PSINR!"
Úloha #4: Bitový chaos
Táto metóda šifrovania ešte nie je nikde publikovaná a v našom tíme bola predmetom dlhých diskusií, počas ktorých sme v blízkych aj vzdialených podnikoch stiahli litre kofoly. Nakoľko je však metodika veľmi jednoduchá, je výsledný efekt úplne geniálny.
Pri šifrovaní postupujte nasledovne: Znak, ktorý má byť zašifrovaný,
si rozdeľte na polovicu (4 bity + 4 bity). Následne
bity v prvej polovici rozdeľte do párov a ich hodnoty v páre navzájom
vymeňte. Takto vytvorenú štvoricu bitov použite pre operáciu
XOR
nad zvyšnými 4 bitmi.
Tento jednoduchý a pritom geniálny princíp si predstavíme na
nasledovnom príklade. Reťazec, ktory je potrebné zašifrovať, je
'H
’:
- Písmeno
'H
’ má v ASCII tabuľke hodnotu 72. Hodnota 72 vyzerá v dvojkovej sústave nasledovne: 01001000. Prvé 4 bity sú teda 0100 a druhé 4 bity sú 1000. - Prvá polovica bitov 0100 pozostáva z dvoch párov (01 a 00), v ktorých jednotlivé bity navzájom vymeníme (z páru 01 sa stane 10 a z páru 00 sa stane pár 00). Tým dostaneme hodnotu 1000.
- Takto upravenú podobu prvých štyroch bitov použijeme ako parameter
operácie
XOR
spolu so zvyšnými štyrmi bitmi:
1000 // druhá polovica
XOR 1000 // prvá polovica po výmene bitov v dvojici
--------
0000
- Spojením oboch získaných štvoríc bitov (prvá štvorica 1000,
ktorú sme získali ako výsledok v 2. kroku a druhá štvorica
0000, ktorú sme získali ako výsledok v 3. kroku) dostaneme
hodnotu 10000000, ktorá predstavuje zašifrovanú podobu písmena
'H
’ pomocou tejto metódy.
V prípade dešifrovania využite opačný postup.
Vašou úlohou je teda vytvoriť funkcie
unsigned char* bit_encrypt(const char* text)
a
char* bit_decrypt(const unsigned char* text)
, pomocou
ktorých budete vedieť zašifrovať a dešifrovať zadanú postupnosť
bytov.
Použitie tejto funkcie je ilustrované na nasledujúcom príklade:
unsigned char* encrypted;
// basic test with long text
= bit_encrypt("Hello world!");
encrypted for(int i=0; i < 12;i++) {
("%x ", encrypted[i]);
printf//80 9c 95 95 96 11 bc 96 b9 95 9d 10
}
(encrypted); free
Úloha #5: Šifra BMP
Ako už bolo uvedené vyššie, za šifrou BMP stojí (niekedy aj sedí) trojica mladých kryptológov Biňas, Madeja a Pietriková a jej jednoduchosť a z nej vyplývajúca genialita vychádza v kombinácii niekoľkých metód súčasne.
Ak chcete reťazec zašifrovať, postupujte nasledovne:
- vstupný reťazec najprv prežeňte funkciou
reverse()
- získaný reťazec následne prežeňte funkciou
vigenere_encrypt()
- výsledný reťazec ešte prežeňte funkciou
bit_encrypt
Pre rozšifrovanie zadanej postupnosti bytov postupujte presne opačne.
Vašou úlohou je teda vytvoriť funkcie
unsigned char* bmp_encrypt(const char* key, const char* text)
a
char* bmp_decrypt(const char* key, const unsigned char* text)
,
pomocou ktorých budete vedieť text zakódovať a dekódovať prostredníctvom
šifry BMP.
Význam jednotlivých parametrov funkcií je nasledovný:
key
- Reťazec reprezentujúci kľúč použitý na zašifrovanie aj odšifrovanie textu. Kľúč môže pozostávať len zo znakov, pričom na veľkosti nezáleží.text
- Reťazec na zašifrovanie resp. odšifrovanie.
Funkcia vráti adresu kópie reťazca zašifrovanú resp. odšifrovanú
pomocou BMP šifry alebo NULL
, ak zašifrovanie
resp. odšifrovanie nebolo úspešné.
Odovzdávanie projektu
Zadanie sa odovzdáva prostredníctvom systému na správu verzií Git na serveri http://git.kpi.fei.tuke.sk/.
Ak vytvárate projekt prvýkrát, urobte tak na tejto stránke. Návod na to, ako si inicializovať prostredie a ako pracovať s GitLab-om, nájdete na stránkach predmetu ZAP.
Štruktúra vášho projektu bude vyzerať nasledovne:
.
├── ps1/
│ ├── playfair.c
│ ├── playfair.h
│ ├── bmp.c
│ ├── bmp.h
│ ├── main.c
│ └── Makefile
└── README
kde význam súborov v priečinku ps1/
je nasledovný:
playfair.c
,playfair.h
- Zdrojový kód a hlavičkový súbor knižnice pre Playfairovu šifru.bmp.c
,bmp.h
- Zdrojový kód a hlavičkový súbor knižnice pre BMP šifru.main.c
- Zdrojový kód obsahujúci funkciumain()
.Makefile
-Makefile
súbor obsahujúci minimálne tieto ciele:playfair.o
pre vytvorenie modulu na prácu s Playfairovou šifrou,bmp.o
pre vytvorenie modulu na prácu so šifrou BMP,all
pre vytvorenie spustiteľného programu s názvomps1
, aclean
pre odstránenie priebežne vytvorených objektových a spustiteľných súborov.
README
- Súbor, v ktorom bude uvedené označenie vašej skupiny, ktorú navštevujete na cvičeniach v tvare:GROUP : A1
Upozornenie
Je dôležité, aby vaše súbory zachovali uvedenú štruktúru. Ak sa niektorý zo súborov síce v repozitári nachádza, ale v inom priečinku, bude to považované za chybu a takýto projekt nebude považovaný za správny! Ak sa naopak vo vašom projekte nachádzajú súbory alebo priečinky navyše, tieto nebudú považované za chybu.
Upozornenie
Pri názvoch priečinkov, súborov a obsahu súboru README
záleží na veľkosti písmen!
Kostra projektu
Z nasledujúceho odkazu si stiahnite súbor top.secret.zip, ktorý obsahuje kostru projektu. Tento balíček obsahuje nasledujúce súbory:
playfair.h
- hlavičkový súbor, v ktorom sa nachádzajú deklarácie všetkých požadovaných funkcií pre Playfairovu šifru.bmp.h
- hlavičkový súbor, v ktorom sa nachádzajú deklarácie všetkých požadovaných funkcií pre BMP šifru.
V prostredí OS Linux môžete pre stiahnutie použiť príkaz
wget
v tvare:
$ wget https://kurzy.kpi.fei.tuke.sk/pvjc/2024/download/top.secret.zip
Hodnotenie a testovanie
V rámci tohto zadania nie je predpísaná grafická podoba výstupu ani dialóg medzi používateľom a počítačom. V tom máte voľnú ruku.
Vaše hodnotenie sa bude odvíjať od výsledku testov, ktorými vaše zadanie úspešne prejde. Overovať sa bude:
- Štruktúra vášho projektu (či sa v ňom nachádzajú všetky potrebné súbory).
- Statická analýza vášho kódu pomocou nástroja
cppcheck
. - Kontrola únikov v pamäti pomocou nástroja
valgrind
- Prítomnosť globálnych premenných vo vašom kóde.
- Funkčnosť vašej implementácie.
Váš kód sa bude prekladať prekladačom gcc s nasledovnými prepínačmi a knižnicami:
$ gcc -std=c11 -Werror -Wall -lm
Testovanie vašich riešení sa bude vykonávať automaticky každé 3 hodiny a to konkrétne o 0300, 0600, 0900, 1200, 1500, 1800, 2100 a 2400.
Vaše riešenia opäť prejdú kontrolou originality. Preto sa pri práci na vašom zadaní správajte podľa pravidiel etického kódexu! V prípade, že odovzdáte zadanie, ktoré nie je vaše, budete vylúčení z predmetu!