Wróć do informacji o e-podręczniku Wydrukuj Pobierz materiał do PDF Pobierz materiał do EPUB Pobierz materiał do MOBI Zaloguj się, aby dodać do ulubionych Zaloguj się, aby skopiować i edytować materiał Zaloguj się, aby udostępnić materiał Zaloguj się, aby dodać całą stronę do teczki

Klucze RSA w języku C++

Specyfikacja problemu:

Dane:

  • p – liczba naturalna pierwsza; jedna z liczb pierwszych potrzebnych do wyznaczenia kluczy

  • q – liczba naturalna pierwsza; druga z liczb pierwszych potrzebnych do wyznaczenia kluczy

Wynik:

  • klucz_prywatny – para liczb naturalnych; klucz prywatny algorytmu RSA

  • klucz_publiczny – para liczb naturalnych; klucz publiczny algorytmu RSA

Przykład 1

Wygenerujmy w języku C++ klucze prywatnyklucz prywatnyprywatnypublicznyklucz publicznypubliczny szyfru 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 Eratostenesasito Eratostenesa (jego implementacja w języku C++ została omówiona w e‑materiale: Sito Eratostenesa w języku C++PIp1MFCmXSito Eratostenesa w języku C++).

Następnie obliczamy wartość n = p ∙ q oraz wartość funkcji Eulerafunkcja Eulerafunkcji Eulera dla n (informacje o niej znajdziesz w e‑materiale Szyrf RSAPl0GrlFTpSzyrf RSA).

W kolejnym kroku należy odnaleźć liczbę e spełniającą warunek (1 < e < phi(n)), która będzie względnie pierwszaliczby względnie pierwszewzględnie pierwsza do liczby phi. W tym celu można posłużyć się podstawową wersją algorytmu Euklidesaalgorytm Euklidesaalgorytmu Euklidesa (jego implementacja w języku C++ została omówiona w e‑materiale: Algorytm Euklidesa w języku C++PRand33dvAlgorytm Euklidesa w języku C++).

W następnej kolejności należy obliczyć liczbę d spełniającą równanie:

d e   m o d   φ ( n ) = 1 ,   l u b   i n a c z e j   d = e 1   m o d   φ ( n )

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 liczby phi,

  • n – p ∙ q, gdzie p i q to liczby pierwsze i p != q,

  • d – odwrotność modulo phi(n) liczby e, gdzie phi(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 ab oraz wskaźniki na liczby, które będą miały spełniać tzw. tożsamość Bezouta, czyli xy. Liczby ab, xy są liczbami całkowitymi. Tożsamość Bezouta wygląda następująco: ax + by = NWD(a,b)

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 by, który będzie równy wartości największego wspólnego dzielnika liczb ab, 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 x1y1, które będą wykorzystane do wyznaczenia końcowych wartości xy 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 xy podajemy wskaźniki na x1y1. 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 xy, tak by spełniały one tożsamość Bezouta, i zwrócenie wyznaczonej rekurencyjnie wartości największego wspólnego dzielnika.

Linia 1. int RozszerzonyEuklides otwórz nawias okrągły int a przecinek int b przecinek int asterysk x przecinek int asterysk y zamknij nawias okrągły. Linia 2. otwórz nawias klamrowy. Linia 3. if otwórz nawias okrągły a znak równości znak równości 0 zamknij nawias okrągły. Linia 4. otwórz nawias klamrowy. Linia 5. asterysk x znak równości 0 przecinek asterysk y znak równości 1 średnik. Linia 6. return b średnik. Linia 7. zamknij nawias klamrowy. Linia 8. int x1 przecinek y1 średnik. Linia 9. int NWD znak równości RozszerzonyEuklides otwórz nawias okrągły b procent a przecinek a przecinek ampersant x1 przecinek ampersant y1 zamknij nawias okrągły średnik. Linia 10. asterysk x znak równości y1 minus otwórz nawias okrągły b prawy ukośnik a zamknij nawias okrągły asterysk x1 średnik. Linia 11. asterysk y znak równości x1 średnik. Linia 12. return NWD średnik. Linia 13. zamknij nawias klamrowy.

Przedstawiona poniżej funkcja pomocnicza ma na celu wyznaczenie odwrotności modulo podanych jej liczb.

Pierwszym krokiem będzie stworzenie zmiennych xy, 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.

Linia 1. int odwrotnoscMod otwórz nawias okrągły int a przecinek int b zamknij nawias okrągły. Linia 2. otwórz nawias klamrowy. Linia 3. int x przecinek y średnik. Linia 4. int g znak równości RozszerzonyEuklides otwórz nawias okrągły a przecinek b przecinek ampersant x przecinek ampersant y zamknij nawias okrągły średnik. Linia 5. if otwórz nawias okrągły g wykrzyknik znak równości 1 zamknij nawias okrągły prawy ukośnik prawy ukośnik jeśli g wykrzyknik znak równości 1 to liczby a i b nie są względnie pierwsze. Linia 6. return minus 1 średnik. Linia 7. else. Linia 8. otwórz nawias klamrowy. Linia 9. prawy ukośnik asterysk Liczba x może być ujemna przecinek aby tego uniknąć. Linia 10. dodajemy b przecinek jeśli liczba jest dodatnia przecinek to zewnętrzna. Linia 11. operacja procent gwarantuje nam poprawny wynik asterysk prawy ukośnik. Linia 12. int odwrotnosc znak równości otwórz nawias okrągły x procent b plus b zamknij nawias okrągły procent b średnik. Linia 13. return odwrotnosc średnik. Linia 14. zamknij nawias klamrowy. Linia 15. zamknij nawias klamrowy.

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ść:

f(P) = Pe mod n

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.

Linia 1. unsigned long long szyfrowanieRSA otwórz nawias okrągły int wiadomosc przecinek int e przecinek int n zamknij nawias okrągły. Linia 2. otwórz nawias klamrowy. Linia 3. return otwórz nawias okrągły unsigned long long zamknij nawias okrągły pow otwórz nawias okrągły wiadomosc przecinek e zamknij nawias okrągły procent n średnik. Linia 4. zamknij nawias klamrowy.

Jeżeli nie jest możliwe użycie funkcji pow(), to funkcja szyfrująca może mieć następującą formę:

Linia 1. unsigned long long szyfrowanieRSA otwórz nawias okrągły int wiadomosc przecinek int e przecinek int n zamknij nawias okrągły. Linia 2. otwórz nawias klamrowy. Linia 3. unsigned long long rezultat znak równości wiadomosc średnik. Linia 4. for otwórz nawias okrągły int i znak równości 1 średnik i otwórz nawias ostrokątny e średnik i plus plus zamknij nawias okrągły. Linia 5. otwórz nawias klamrowy. Linia 6. rezultat znak równości otwórz nawias okrągły rezultat asterysk wiadomosc zamknij nawias okrągły procent n średnik. Linia 7. zamknij nawias klamrowy. Linia 8. return rezultat średnik. Linia 9. zamknij nawias klamrowy.

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ść:

f1(C) = Cd mod n
Linia 1. unsigned long long deszyfrowanieRSA otwórz nawias okrągły int szyfrogram przecinek int d przecinek int n zamknij nawias okrągły. Linia 2. otwórz nawias klamrowy. Linia 3. return otwórz nawias okrągły unsigned long long zamknij nawias okrągły pow otwórz nawias okrągły szyfrogram przecinek d zamknij nawias okrągły procent n średnik. Linia 4. zamknij nawias klamrowy.

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ć:

Linia 1. unsigned long long deszyfrowanieRSA otwórz nawias okrągły int szyfrogram przecinek int d przecinek int n zamknij nawias okrągły. Linia 2. otwórz nawias klamrowy. Linia 3. unsigned long long rezultat znak równości szyfrogram średnik. Linia 4. for otwórz nawias okrągły int i znak równości 1 średnik i otwórz nawias ostrokątny d średnik i plus plus zamknij nawias okrągły. Linia 5. otwórz nawias klamrowy. Linia 6. rezultat znak równości otwórz nawias okrągły rezultat asterysk szyfrogram zamknij nawias okrągły procent n średnik. Linia 7. zamknij nawias klamrowy. Linia 8. return rezultat średnik. Linia 9. zamknij nawias klamrowy.

Problemy w implementacji szyfru RSA w języku C++

Specyfikacja problemu:

Dane:

  • p – liczba naturalna pierwsza; jedna z liczb pierwszych potrzebnych do wyznaczenia kluczy

  • q – liczba naturalna pierwsza; druga z liczb pierwszych potrzebnych do wyznaczenia kluczy

  • wiadomoscSzyfrowana – 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.

Przykład 2

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:

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 2. kratka include otwórz nawias ostrokątny cmath zamknij nawias ostrokątny. Linia 4. using namespace std średnik. Linia 6. int RozszerzonyEuklides otwórz nawias okrągły int a przecinek int b przecinek int asterysk x przecinek int asterysk y zamknij nawias okrągły. Linia 7. otwórz nawias klamrowy. Linia 8. if otwórz nawias okrągły a znak równości znak równości 0 zamknij nawias okrągły. Linia 9. otwórz nawias klamrowy. Linia 10. asterysk x znak równości 0 przecinek asterysk y znak równości 1 średnik. Linia 11. return b średnik. Linia 12. zamknij nawias klamrowy. Linia 13. int x1 przecinek y1 średnik. Linia 14. int NWD znak równości RozszerzonyEuklides otwórz nawias okrągły b procent a przecinek a przecinek ampersant x1 przecinek ampersant y1 zamknij nawias okrągły średnik. Linia 15. asterysk x znak równości y1 minus otwórz nawias okrągły b prawy ukośnik a zamknij nawias okrągły asterysk x1 średnik. Linia 16. asterysk y znak równości x1 średnik. Linia 18. return NWD średnik. Linia 19. zamknij nawias klamrowy. Linia 21. int odwrotnoscMod otwórz nawias okrągły int a przecinek int b zamknij nawias okrągły. Linia 22. otwórz nawias klamrowy. Linia 23. int x przecinek y średnik. Linia 24. int g znak równości RozszerzonyEuklides otwórz nawias okrągły a przecinek b przecinek ampersant x przecinek ampersant y zamknij nawias okrągły średnik. Linia 25. if otwórz nawias okrągły g wykrzyknik znak równości 1 zamknij nawias okrągły prawy ukośnik prawy ukośnik jeśli g wykrzyknik znak równości 1 to liczba nie jest pierwsza. Linia 26. return minus 1 średnik. Linia 27. else. Linia 28. otwórz nawias klamrowy. Linia 29. prawy ukośnik asterysk Liczba x może być ujemna przecinek aby tego uniknąć. Linia 30. dodajemy m przecinek jeśli liczba jest dodatnia przecinek to zewnętrzna. Linia 31. operacja procent gwarantuje nam poprawny wynik asterysk prawy ukośnik. Linia 32. int odwrotnosc znak równości otwórz nawias okrągły x procent b plus b zamknij nawias okrągły procent b średnik. Linia 33. return odwrotnosc średnik. Linia 34. zamknij nawias klamrowy. Linia 35. zamknij nawias klamrowy. Linia 37. unsigned long long szyfrowanieRSA otwórz nawias okrągły int wiadomosc przecinek int e przecinek int n zamknij nawias okrągły. Linia 38. otwórz nawias klamrowy. Linia 39. return otwórz nawias okrągły unsigned long long zamknij nawias okrągły pow otwórz nawias okrągły wiadomosc przecinek e zamknij nawias okrągły procent n średnik. Linia 40. zamknij nawias klamrowy. Linia 42. unsigned long long deszyfrowanieRSA otwórz nawias okrągły int szyfrogram przecinek int d przecinek int n zamknij nawias okrągły. Linia 43. otwórz nawias klamrowy. Linia 44. return otwórz nawias okrągły unsigned long long zamknij nawias okrągły pow otwórz nawias okrągły szyfrogram przecinek d zamknij nawias okrągły procent n średnik. Linia 45. zamknij nawias klamrowy. Linia 47. int funkcjaEulera otwórz nawias okrągły int p przecinek int q zamknij nawias okrągły. Linia 48. otwórz nawias klamrowy. Linia 49. return otwórz nawias okrągły p minus 1 zamknij nawias okrągły asterysk otwórz nawias okrągły q minus 1 zamknij nawias okrągły średnik. Linia 50. zamknij nawias klamrowy. Linia 52. int main otwórz nawias okrągły zamknij nawias okrągły. Linia 53. otwórz nawias klamrowy. Linia 54. prawy ukośnik prawy ukośnik Jako osoba chcąca odbierać zaszyfrowane wiadomości generuję sobie klucz prywatny i publiczny. Linia 55. int p znak równości 5 średnik. Linia 56. int q znak równości 7 średnik. Linia 57. int n znak równości p asterysk q średnik. Linia 58. int euler znak równości funkcjaEulera otwórz nawias okrągły p przecinek q zamknij nawias okrągły średnik. Linia 59. int e znak równości 1 średnik. Linia 60. int pom1 średnik prawy ukośnik prawy ukośnik zmienne pomocnicze. Linia 61. int pom2 średnik prawy ukośnik prawy ukośnik Aby użyć rozszerzonego algorytmu Euklidesa jak zwykłego. Linia 62. do prawy ukośnik prawy ukośnik odnajdywanie e takiego przecinek źe 1 otwórz nawias ostrokątny e otwórz nawias ostrokątny euler. Linia 63. otwórz nawias klamrowy. Linia 64. e plus plus średnik. Linia 65. zamknij nawias klamrowy. Linia 66. while otwórz nawias okrągły RozszerzonyEuklides otwórz nawias okrągły e przecinek euler przecinek ampersant pom1 przecinek ampersant pom2 zamknij nawias okrągły wykrzyknik znak równości 1 zamknij nawias okrągły średnik. Linia 67. int d znak równości odwrotnoscMod otwórz nawias okrągły e przecinek euler zamknij nawias okrągły średnik. Linia 68. prawy ukośnik asterysk e przecinek n minus klucz publiczny. Linia 69. d przecinek n minus klucz prywatny asterysk prawy ukośnik. Linia 70. int wiadomoscSzyfrowana znak równości 32 średnik. Linia 71. int szyfrogram znak równości szyfrowanieRSA otwórz nawias okrągły wiadomoscSzyfrowana przecinek e przecinek n zamknij nawias okrągły średnik. Linia 72. cout otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów Szyfrogram to dwukropek cudzysłów otwórz nawias ostrokątny otwórz nawias ostrokątny szyfrogram otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów cudzysłów otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 74. return 0 średnik. Linia 75. zamknij nawias klamrowy.

Wynik działania programu:

Linia 1. Szyfrogram to dwukropek 2.

Wynikiem działania programu, szyfrowania liczby naturalnej, jest inna liczba naturalna.

Słownik

algorytm Euklidesa
algorytm Euklidesa

algorytm służący do wyznaczania największego wspólnego dzielnika (NWD) podanych dwóch liczb całkowitych

funkcja Eulera
funkcja Eulera

(funkcja ) funkcja przypisująca każdej liczbie naturalnej liczbę liczb względnie pierwszych z nią i nie większych od niej

klucz prywatny
klucz prywatny

rodzaj klucza służącego w szyfrach asymetrycznych do deszyfrowania szyfrogramów; dostęp do niego powinien mieć tylko odbiorca zaszyfrowanych wiadomości

klucz publiczny
klucz publiczny

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 względnie pierwsze
liczby względnie pierwsze

liczby całkowite, których największym wspólnym dzielnikiem jest 1

sito Eratostenesa
sito Eratostenesa

algorytm wyznaczania liczb pierwszych w zadanym przedziale