Implementacja algorytmu RSA
Klucze RSA w języku C++
Specyfikacja problemu:
Dane:
p– liczba naturalna pierwsza; jedna z liczb pierwszych potrzebnych do wyznaczenia kluczyq– liczba naturalna pierwsza; druga z liczb pierwszych potrzebnych do wyznaczenia kluczy
Wynik:
klucz_prywatny– para liczb naturalnych; klucz prywatny algorytmu RSAklucz_publiczny– para liczb naturalnych; klucz publiczny algorytmu RSA
Algorytm szyfrowania RSA wymaga zaimplementowaniu kilku funkcji pomocniczych lub zastosowania odpowiednich bibliotek. Jego tworzenie rozpoczniemy od wybrania dwóch liczb pierwszych i oznaczenia ich jako p i q. Do ich odnalezienia możemy zastosować sito Eratostenesasito Eratostenesa (jego implementacja w języku C++ została omówiona w e‑materiale: Sito Eratostenesa w języku C++Sito Eratostenesa w języku C++).
Następnie obliczamy wartość n = p ∙ q oraz wartość funkcji Eulerafunkcji Eulera dla n (informacje o niej znajdziesz w e‑materiale Szyrf RSASzyrf RSA).
W kolejnym kroku należy odnaleźć liczbę e spełniającą warunek (1 < e < phi(n)), która będzie względnie pierwszawzględnie pierwsza do liczby phi. W tym celu można posłużyć się podstawową wersją algorytmu Euklidesaalgorytmu Euklidesa (jego implementacja w języku C++ została omówiona w e‑materiale: Algorytm Euklidesa w języku C++Algorytm Euklidesa w języku C++).
W następnej kolejności należy obliczyć liczbę d spełniającą równanie:
Aby to osiągnąć, można posłużyć się rozszerzonym algorytmem Euklidesa, również przedstawionym we wspomnianym wyżej materiale.
Kluczem publicznym jest para liczb (e, n), a kluczem prywatnym (d, n), gdzie:
e– liczba względnie pierwsza do liczbyphi,n–p ∙ q, gdziepiqto liczby pierwsze ip != q,d– odwrotność modulophi(n)liczbye, gdziephi(n)to funkcja Eulera.
Przedstawiona poniżej funkcja pozwala na wyznaczenie największego wspólnego dzielnika lub sprawdzenie, czy podane liczby są względnie pierwsze.
Funkcja przyjmuje cztery argumenty: liczby, dla których szukamy największego wspólnego dzielnika, czyli a i b oraz wskaźniki na liczby, które będą miały spełniać tzw. tożsamość Bezouta, czyli x i y. Liczby a, b, x i y są liczbami całkowitymi. Tożsamość Bezouta wygląda następująco:
Przedstawiana funkcja będzie opierała się na mechanizmie rekurencji, w związku z czym wymaga ona warunku zakończenia, po którego spełnieniu zwróci wartość, co pozwoli na zakończenie rekurencji (zapobiegnie powstaniu nieskończonej pętli). Dla rozszerzonego algorytmu Euklidesa warunkiem tym jest osiągnięcie przez pierwszą z porównywanych liczb wartości 0. Wtedy jedynym liczącym się współczynnikiem w tożsamości Bezouta będzie współczynnik , który będzie równy wartości największego wspólnego dzielnika liczb a i b, więc wartość x ustawiamy na 0, a y na 1 (zerujemy pierwszy wyraz równania, a drugi przyjmuje wartość b).
Następnym krokiem jest stworzenie zmiennych x1 i y1, które będą wykorzystane do wyznaczenia końcowych wartości x i y po zakończeniu rekurencji, w każdym wywołaniu funkcji.
Kolejnym etapem tego algorytmu będzie rekurencyjne wywołanie funkcji ze zmienionymi argumentami. Pierwszy argument powinien przyjąć wartość b%a, a jako drugi podajemy dotychczasowy argument a. W miejsce x i y podajemy wskaźniki na x1 i y1. W ten sposób uzyskamy cykl rekurencji zakończony w momencie wystąpienia warunku zakończenia – zredukowania pierwotnych wartości porównywanych do jednej różnej od 0, będącej ich największym wspólnym dzielnikiem.
Ostatni etap to modyfikacja wartości x i y, tak by spełniały one tożsamość Bezouta, i zwrócenie wyznaczonej rekurencyjnie wartości największego wspólnego dzielnika.
Przedstawiona poniżej funkcja pomocnicza ma na celu wyznaczenie odwrotności modulo podanych jej liczb.
Pierwszym krokiem będzie stworzenie zmiennych x i y, których adresy następnie zostaną użyte przy wywołaniu poprzednio opisanej funkcji.
Aby możliwe było wykonanie operacji odwrotności modulo, liczby, na których chcemy wykonać tę operację, muszą być względnie pierwsze. Sprawdzamy to za pomocą opisanej wcześniej funkcji realizującej rozszerzony algorytm Euklidesa.
Jeśli funkcja obliczająca największy wspólny dzielnik zwróciła wartość inną niż 1, oznacza to, że liczby, na których chcemy wykonać operację odwrotności modulo, nie są względnie pierwsze, zatem powinniśmy zakończyć wykonywanie funkcji z kodem oznaczającym błąd.
Jeżeli liczby, na których chcemy wykonać działanie, są względnie pierwsze, obliczamy odwrotność modulo z wykorzystaniem wyznaczonej przez rozszerzony algorytm Euklidesa wartości x i zwracamy wynik.
W tym momencie posiadamy już wszystkie narzędzia do wyznaczania klucza publicznego, klucza prywatnego oraz do szyfrowania i deszyfrowania wiadomości.
Aby zaszyfrować wiadomość, dzielimy ją na bloki o wartości liczbowej nie większej niż n, a następnie implementujemy następującą funkcję, w której P oznacza szyfrowaną wiadomość:
Warto zwrócić uwagę, że wartości (e, n) stanowią klucz publiczny osoby, do której wysyłamy wiadomość.
Funkcje szyfrującą i deszyfrującą łatwo jest zaimplementować z wykorzystaniem funkcji pow(), dostępnej w bibliotece cmath.
Jeżeli nie jest możliwe użycie funkcji pow(), to funkcja szyfrująca może mieć następującą formę:
Aby odszyfrować wiadomość, wykonujemy następującą funkcję, w której C oznacza szyfrogram, a para (d, n) jest kluczem prywatnym osoby, która odebrała tę wiadomość:
Podobnie jak w poprzednim przypadku – jeżeli nie jest możliwe użycie funkcji pow(), to funkcja szyfrująca może przyjąć następującą postać:
Problemy w implementacji szyfru RSA w języku C++
Specyfikacja problemu:
Dane:
p– liczba naturalna pierwsza; jedna z liczb pierwszych potrzebnych do wyznaczenia kluczyq– liczba naturalna pierwsza; druga z liczb pierwszych potrzebnych do wyznaczenia kluczywiadomoscSzyfrowana– liczba naturalna; liczba szyfrowana
Wynik:
szyfrogram– zaszyfrowana wiadomość
Implementując szyfr RSA w języku C++, możemy napotkać problemy, wynikające z ograniczeń samego języka. Liczby pierwsze używane do generowania klucza publicznego i prywatnego powinny być „duże”. Język C++ domyślnie oferuje jedynie liczby 8‑bajtowe bez znaku (unsigned long long) – liczba taka przyjmuje wartości od 0 do 18 446 744 073 709 551 615. Nie jest ona wystarczająca dla większych wartości kluczy. Dlatego używanie tego algorytmu w języku C++ bez wprowadzania nowych typów danych bardzo ogranicza jego możliwości.
Dla większych wartości kluczy zakres zmiennej w postaci unsigned long long może być niewystarczający. Dla danych: p = 17, q = 29 i e = 3 otrzymujemy n = 493 oraz d = 299. Zmienna wiadomoscSzyfrowana podnoszona jest do trzeciej potęgi, co w przypadku „większych” wiadomości może okazać się za dużą wartością, nawet dla zmiennej typu unsigned long long. Dodatkowo podczas deszyfrowania szyfrogram podnoszony jest do 299 potęgi, co dla dowolnej liczby większej od 1 daje wynik, którego wartość zdecydowanie wykracza poza zakres. Jest to przypadek skrajny, jednak wyraźnie pokazuje, że używanie szyfru RSA wymaga dużej mocy obliczeniowej.
Przykładowy program:
Wynik działania programu:
Wynikiem działania programu, szyfrowania liczby naturalnej, jest inna liczba naturalna.
Słownik
algorytm służący do wyznaczania największego wspólnego dzielnika (NWD) podanych dwóch liczb całkowitych
(funkcja ) funkcja przypisująca każdej liczbie naturalnej liczbę liczb względnie pierwszych z nią i nie większych od niej
rodzaj klucza służącego w szyfrach asymetrycznych do deszyfrowania szyfrogramów; dostęp do niego powinien mieć tylko odbiorca zaszyfrowanych wiadomości
rodzaj klucza służącego w szyfrach asymetrycznych do szyfrowania tekstu jawnego; nie musi być utrzymywany w sekrecie, a nawet może zostać udostępniony publicznie
liczby całkowite, których największym wspólnym dzielnikiem jest 1
algorytm wyznaczania liczb pierwszych w zadanym przedziale