Strona główna » Algorytmy » Szyfry » Klucz Jednorazowy
 

Klucz Jednorazowy

Szyfr

Szyfrowanie kluczem jednorazowym jest sposobem szyfrowania danych jest praktycznie nie do złamania. Zasada szyfrowania opiera się na wykonaniu operacji XOR na każdej parze bitów z tekstu jawnego oraz klucza. Jednak, aby zaszyfrowane dane były rzeczywiście nie do złamania należy ustalić taki klucz, aby długość szyfrowania była co najmniej długości szyfrowanego tekstu. Oczywiście klucz musi być znany dla obu stron. Zaletą takiego szyfrowania jest odporność na złamania typu brute-force, ponieważ podczas próby takiego ataku można uzyskać wszystkie możliwe wiadomości o długości równej długości tekstu jawnego. Dodatkową zasadą, która odpowiada za wysoki poziom ochrony danych jest nieujawnianie klucza i użycie każdego klucza tylko raz!

Przykład

Jako przykład załóżmy, że będziemy szyfrować słowo INFORMACJA. Zakładamy, że dana litera ma wartość równą jej pozycji w alfabecie łacińskim pomniejszoną o jeden czyli A to 00000, B to 00001 itd. Każda taka wartość jest zakodowana przy pomocy 5 bitów. Użyty klucz jest zapisany szesnastkowo i wynosi 102A3BFA1601A i każdemu znakowi odpowiadają dokładnie 4 bity. Pierwsza czynność polega na zamianie tekstu jawnego oraz klucza na postać binarną:

  1. I    N    F    O    R    M    A    C    J    A
  2. 01000011010010101110100010110000000000100100100000
  1. 1   0   2   A   3   B   F   A   1   6   0   1   A
  2. 0001000000101010001110111111101000010110000000011010

Kolejny etap polega na wykonaniu operacji XOR dla każdej pary bitów z szyfrowanego tekstu i klucza. Należy pamiętać, że zawijanie klucza spowoduje obnizenie poziomu ochrony danych.

  1. tekst jawny: 01000011010010101110100010110000000000100100100000
  2.       klucz: 00010000001010100011101111111010000101100000000110
  3.  szyfrogram: 01010011011000001101001101001010000101000100100110

Następnie szyfrogram w postaci binarnej lepiej jest przetłumaczyć na bardziej zrozumiałą treść. W tym celu można zastosować dzielenie po 5 bitów tak jak kodowany jest tekst jawny. Wtedy otrzymany szyfrogram to:

  1. 01010011011000001101001101001010000101000100100110
  2. K    N    Q    N    G    S    Q    U    J    G

Otrzymany szyfrogram to KNQNGSQUJ. Jak widać litera N z tekstu jawnego nie uległa zmianie, ale należy pamiętać, że nie jest to reguła. Poza tym znajomość jednej litery nie wypływa na znajomość tekstu jawnego, ponieważ jest ona zaszyfrowana innym fragmentem klucza niż dowolny inny znak w tekście jawnym. Może się zdarzyć, że wartość grupy 5 bitów będzie większa niż wartość ostatniej litery w alfabecie. W tym przypadku dotyczy to wartości większej lub równej 26. Wtedy wystarczy wykorzystać funkcję modulo.

Deszyfrowanie

Deszyfrowanie danych ma odwrotną listę kroków do szyfrowania. Na początek należy szyfrogram i klucz zamienić na postać binarną. Następnie na każdej parze bitów obliczyć wartość XOR, a następnie wybrać odpowiednią ilość bitów, aby uzyskać znaki tekstu jawnego. W przykładzie zgodnie z szyfrowaniem grupowania odbywałoby się po 5 bitów.

W rzeczywistości cały proces sprowadza się do zaszyfrowania szyfrogramu przy pomocy klucza.

Implementacja

Założenia

Zakładamy, że szyfrowany tekst to duże litery alfabetu łacińskiego szyfrowane po 5 bitów, które reprezentują odległość danej litery od litery A w alfabecie. Ze względu na fakt, że do poprawnego szyfrowania potrzebne jest 32 znaki to za alfabet zostało przyjęte kolejne 32 znaki w tablicy ASCII począwszy od A włącznie. Program wczytuje od użytkownika klucz, którego kolejne znaki to wartości zapisane szesnastkowo, których zapis binarny tworzy klucz. Podczas szyfrowania nieznanego znaku należy wstawić znak spacji, a w przypadku nieprawidłowej długości klucza należy wyświetlić odpowiedni komunikat.

Poprawność klucza

Przed przystąpieniem do szyfrowania należy upewnić się, że dany klucz jest prawidłowy. Zgodnie z założeniem powinien być on reprezentowany przez cyfry systemu szesnastkowego. Oznacza to, że należy sprawdzić czy wszystkie znaki mogą być cyframi liczby zapisanej szesnastkowo. Drugi warunek polega na sprawdzeniu czy dla każdego bitu z tekstu szyfrowanego istnieje powiązany bit w kluczu.

  1. bool validKey(char* txt, char* key) {
  2.   if (strlen(key) * 4 < strlen(txt) * 5)
  3.     return false;
  4.   for (int i = 0; key[i]; i++)
  5.     if ((key[i] < 'A' && key[i] > 'F') || (key[i] < '0' && key[i] > '9'))
  6.       return false;
  7.   return true;
  8. }

(1.) Funkcja przyjmuje dwa argumenty: txt - tekst szyfrowany oraz key - klucz szyfrujący. (2.) Sprawdź czy długość klucza jest odpowiednia i jeśli nie to (3.) zwróć, że klucz jest nieprawidłowy. (4.) Sprawdź także drugi warunek: (5.) czy każdy znak klucza jest prawidłową wartością i jeśli (6.) wykryjesz problem to (6.) zwróć fałsz.

Wartość cyfry szesnastkowej

Druga funkcja pomocnicza keyValue() pozwala odczytać zakodowaną wartość po dowolnym znakiem cyfry szesnastkowej. Ponadto przyjmuje ona indeks key_i. Dzięki temu po pobraniu kolejnej wartości z listy indeks jest automatycznie zwiększany na podstawie której funkcja pobiera wartość cyfry.

  1. int keyValue(char* key, int &key_i) {
  2.   if (key[key_i] >= '0' && key[key_i] <= '9')
  3.     return key[key_i++] - '0';
  4.   return (key[key_i++] - 'A') + 10;
  5. }

(2.) W przypadku znaku będącego cyfrą (3.) zwróć wartość cyfr, a w innym przypadku (4.) zwróć odległość litery od A zwiększone o 10.

Szyfrowanie

Funkcja szyfrująca alokuje nowe miejsce w pamięci pod tekst wynikowy. Tablica wynikowa będzie miała litery zakodowane zgodnie z kodowaniem ASCII, ale podczas szyfrowania każdy znak zostanie zaszyfrowany po odpowiednim przeliczeniu wartości. cipher() przyjmuje dwa argumenty: txt - tekst do zaszyfrowania oraz key - klucz szyfrujący. Funkcja wymaga wcześniejszego sprawdzenia klucza!

  1. char* cipher(char* txt, char* key) {
  2.   char* txtc = new char[strlen(txt) + 1];
  3.   int key_i = 0, key_bit = 8, key_znak = keyValue(key, key_i);
  4.   for (int i = 0; txt[i]; i++) {
  5.     char znak = (txt[i] - 'A');
  6.     if (znak >= 0 && znak <= 31) {
  7.       char szyfr = 0;
  8.       for (int znak_bit = 16; znak_bit > 0; znak_bit /= 2) {
  9.         szyfr += znak_bit * (((znak & znak_bit) == znak_bit) ^ ((key_znak & key_bit) == key_bit));
  10.         key_bit /= 2;
  11.         if (key_bit == 0) {
  12.           key_znak = keyValue(key, key_i);
  13.           key_bit = 8;
  14.         }
  15.       }
  16.       txtc[i] = szyfr + 'A';
  17.     } else {
  18.       txtc[i] = ' ';
  19.     }
  20.   }
  21.   txtc[strlen(txt)] = '\0';
  22.   return txtc;
  23. }

(2.) Alokacja pod tekst wynikowy. (3.) Zadeklaruj następujące zmienne: key_i - określa indeks znaku klucza, który powinien być wybrany jako następny znak do szyfrowania, key_bit - określa maskę, który bit znaku szyfrującego powinien być pobrany oraz key_znak - aktualny znak szyfrujący.

(4.) Dla każdego kolejnego znaku w tekście szyfrowanym: (5.) oblicz pozycję szyfrowanego znaku w używanym alfabecie. (6.) Jeśli szyfrowany znak należy do alfabetu to: (7.) rozpocznij ustalanie nowego znaku. (8.) Dla każdego bitu szyfrowanego znaku: (9.) oblicz na kolejnych pozycjach wartość funkcji XOR. (10. - 14.) W przypadku, gdy dany znak szyfrujący nie ma już bitów to pobierz następny. (16.) Ustalony znak dopisz do tekstu wynikowego pamiętając, aby był zakodowany zgodnie z tablicą ASCII. W przypadku, gdy znak nie należy do alfabetu to (18.) dopisz znak spacji. Na koniec (21.) dopisz znak końca danych i (22.) zwróć wskaźnik na odpowiednie miejsce w pamięci.

Deszyfrowanie

Funkcja deszyfrujący decipher() polega na zaszyfrowaniu szyfrogramu kluczem, więc jest to jedynie alias funkcji cipher().

  1. char* decipher(char* txtc, char* key) {
  2.   return cipher(txtc, key);
  3. }

Testowanie funkcji

Poniższa funkcja main() wczytuje od użytkownika tekst do zaszyfrowania oraz klucz. Następnie jeśli klucz jest prawidłowy to zostanie pokazany tekst zaszyfrowany oraz rozszyfrowany.

  1. int main () {
  2.   char* txt = new char[512];
  3.   char* key = new char[512];
  4.   cout << "Podaj tekst do zaszyfrowania:\n";
  5.   cin.getline(txt, 512);
  6.   cout << "Podaj klucz:\n";
  7.   cin.getline(key, 512);
  8.   if (validKey(txt, key)) {
  9.     char* txtc = cipher(txt, key);
  10.     cout << "Zaszyfrowany tekst:\n" << txtc << endl;
  11.     char* txtd = decipher(txtc, key);
  12.     cout << "Rozszyfrowany tekst:\n" << txtd << endl;
  13.     delete txtc, txtd;
  14.   } else {
  15.     cout << "Podany klucz jest nieprawidłowy!";
  16.   }
  17.   delete txt, key;
  18.   system("pause");
  19.   return 0;
  20. }

Zadania

Zadanie 1

Napisz program, który nie będzie wczytywał od użytkownika klucza. Klucz powinien zostać wygenerowany losowo, ale bez losowania cyfr z zapisu szesnastkowego, a przez losowanie kolejnych bitów. Pamiętaj, aby wyświetlić klucz przed wypisaniem szyfrogramu.

Przykładowo dla danych:

  1. INFORMACJA

Program wypisze przykładowo na ekran:

  1. Wylosowano klucz:
  2. 113181CBAEE86
  3. Zaszyfrowany tekst:
  4. KJ^WS_^MUB