Laboratorium kryptograficzne dla licealistów 6

Podobne dokumenty
Podstawy programowania 2. Temat: Funkcje i procedury rekurencyjne. Przygotował: mgr inż. Tomasz Michno

Wstęp do Informatyki zadania ze złożoności obliczeniowej z rozwiązaniami

Algorytmy w teorii liczb

Laboratorium kryptograficzne dla licealistów 4

Widoczność zmiennych Czy wartości każdej zmiennej można zmieniać w dowolnym miejscu kodu? Czy można zadeklarować dwie zmienne o takich samych nazwach?

6. Pętle while. Przykłady

Laboratorium kryptograficzne dla licealistów 3

Największy wspólny dzielnik Algorytm Euklidesa (także rozszerzony) WZAiP1: Chińskie twierdzenie o resztach

Algorytmy i struktury danych. Wykład 4

Matematyka dyskretna. Andrzej Łachwa, UJ, /14

Dr inż. Robert Wójcik, p. 313, C-3, tel Katedra Informatyki Technicznej (K-9) Wydział Elektroniki (W-4) Politechnika Wrocławska

Rekurencja (rekursja)

ALGORYTMY I STRUKTURY DANYCH

Piotr Chrząstowski-Wachtel Uniwersytet Warszawski. Al Chwarizmi i trzy algorytmy Euklidesa

Laboratorium kryptograficzne dla licealistów 1

Laboratorium kryptograficzne dla licealistów 3

Wykład 4. Określimy teraz pewną ważną klasę pierścieni.

ALGORYTMY MATEMATYCZNE Ćwiczenie 1 Na podstawie schematu blokowego pewnego algorytmu (rys 1), napisz listę kroków tego algorytmu:

Przykładowe zadania z teorii liczb

Algebra Liniowa 2 (INF, TIN), MAP1152 Lista zadań

0 --> 5, 1 --> 7, 2 --> 9, 3 -->1, 4 --> 3, 5 --> 5, 6 --> 7, 7 --> 9, 8 --> 1, 9 --> 3.

Laboratorium kryptograficzne dla licealistów 1

LICZBY PIERWSZE. Jan Ciurej Radosław Żak

4. Postęp arytmetyczny i geometryczny. Wartość bezwzględna, potęgowanie i pierwiastkowanie liczb rzeczywistych.

Algorytm. a programowanie -

Wybrane zagadnienia teorii liczb

Laboratorium kryptograficzne dla licealistów 2

Wstęp do programowania

Logarytmy. Funkcje logarytmiczna i wykładnicza. Równania i nierówności wykładnicze i logarytmiczne.

Teoria liczb. Magdalena Lemańska. Magdalena Lemańska,

Zadanie 3 Oblicz jeżeli wiadomo, że liczby 8 2,, 1, , tworzą ciąg arytmetyczny. Wyznacz różnicę ciągu. Rozwiązanie:

MADE IN CHINA czyli SYSTEM RESZTOWY

Wyszukiwanie binarne

1. Napisz program, który wyświetli Twoje dane jako napis Witaj, Imię Nazwisko. 2. Napisz program, który wyświetli wizytówkę postaci:

5. Rekurencja. Przykłady

FUNKCJA REKURENCYJNA. function s(n:integer):integer; begin if (n>1) then s:=n*s(n-1); else s:=1; end;

Algorytm i złożoność obliczeniowa algorytmu

Zadania do samodzielnego rozwiązania

Zaawansowane algorytmy i struktury danych

Rekurencja. Przykład. Rozważmy ciąg

Podstawy programowania. Wykład: 13. Rekurencja. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Wstęp do programowania

Algorytmy i język C++

Podstawy programowania. Wykład: 4. Instrukcje sterujące, operatory. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Kryptografia-0. przykład ze starożytności: około 489 r. p.n.e. niewidzialny atrament (pisze o nim Pliniusz Starszy I wiek n.e.)

Szukanie rozwiązań funkcji uwikłanych (równań nieliniowych)

WYRAŻENIA ALGEBRAICZNE

Informatyka I: Instrukcja 4.2

2. Liczby pierwsze i złożone, jednoznaczność rozkładu na czynniki pierwsze, największy wspólny dzielnik, najmniejsza wspólna wielokrotność.

Algorytmy i struktury danych

Laboratorium kryptograficzne dla gimnazjalistów 1

Matematyka dyskretna. Andrzej Łachwa, UJ, a/15

Samodzielnie wykonaj następujące operacje: 13 / 2 = 30 / 5 = 73 / 15 = 15 / 23 = 13 % 2 = 30 % 5 = 73 % 15 = 15 % 23 =

1. Wykład NWD, NWW i algorytm Euklidesa.

Wrocław, Wstęp do informatyki i programowania: liczby pierwsze. Wydział Matematyki Politechniki Wrocławskiej.

Programowanie - wykład 4

Twierdzenie Eulera. Kongruencje wykład 6. Twierdzenie Eulera

Zastosowanie teorii liczb w kryptografii na przykładzie szyfru RSA

Wieczorowe Studia Licencjackie Wrocław, Wykład nr 6 (w oparciu o notatki K. Lorysia, z modyfikacjami) Sito Eratostenesa

Laboratorium kryptograficzne dla gimnazjalistów 3

Funkcja kwadratowa. f(x) = ax 2 + bx + c = a

Zarys algorytmów kryptograficznych

Warunki logiczne instrukcja if

Matematyka dyskretna

INŻYNIERIA BEZPIECZEŃSTWA LABORATORIUM NR 2 ALGORYTM XOR ŁAMANIE ALGORYTMU XOR

Algorytmy asymetryczne

2.8. Algorytmy, schematy, programy

Zadanie 1. Test (6 pkt) Zaznacz znakiem X w odpowiedniej kolumnie P lub F, która odpowiedź jest prawdziwa, a która fałszywa.

Zadanie 1. Potęgi (14 pkt)

Zadanie 1. Algorytmika ćwiczenia

Złożoność informacyjna Kołmogorowa. Paweł Parys

Wykład VII. Kryptografia Kierunek Informatyka - semestr V. dr inż. Janusz Słupik. Gliwice, Wydział Matematyki Stosowanej Politechniki Śląskiej

Programowanie w Baltie klasa VII

Podstawy systemów kryptograficznych z kluczem jawnym RSA

Podstawy programowania. Podstawy C# Przykłady algorytmów

7. Pętle for. Przykłady

Programowanie dynamiczne

Informatyka A. Algorytmy

Kongruencje twierdzenie Wilsona

Podstawy programowania funkcjonalnego

PROGRAMOWANIE W PYTHONIE OD PIERWSZYCH KROKÓW

Matematyka dyskretna

LICZBY PIERWSZE. 14 marzec Jeśli matematyka jest królową nauk, to królową matematyki jest teoria liczb. C.F.

Matematyka dyskretna. Andrzej Łachwa, UJ, /15

ZADANIE 1. Ważenie (14 pkt)

Kryptografia. z elementami kryptografii kwantowej. Ryszard Tanaś Wykład 6a

Zadania język C++ Zad. 1. Napisz program wczytujący z klawiatury wiek dwóch studentów i wypisujący informację o tym, który z nich jest starszy.

Jeszcze o algorytmach

Obóz Naukowy Olimpiady Matematycznej Gimnazjalistów

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Rekurencja. Rekurencja zwana także rekursją jest jedną z najważniejszych metod konstruowania rozwiązań i algorytmów.

Podstawą w systemie dwójkowym jest liczba 2 a w systemie dziesiętnym liczba 10.

CIĄGI wiadomości podstawowe

Kongruencje pierwsze kroki

Liczby całkowite. Zadania do pierwszych dwóch lekcji

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Algorytm selekcji Hoare a. Łukasz Miemus

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

Luty 2001 Algorytmy (7) 2000/2001

Transkrypt:

Laboratorium kryptograficzne dla licealistów 6 Projekt Matematyka dla ciekawych świata Łukasz Mazurek 11.05.2017 1 Potęgowanie W kryptografii często wykonuje się operację potęgowania modulo. Np. w algorytmie RSA, aby zaszyfrować wiadomość m kluczem e należy wykonać działanie c = m e (mod n) W praktyce liczby e i n są bardzo duże (np. mają 1000 cyfr), dlatego ważne jest, aby operacja ta była wykonywana szybko. Następująca funkcja oblicza powyższe wyrażenie dla n = 1 000 000 009, m = 2 i e = 1 000 000: def potega (m, e, n): c = 1 for i in range(e): c = c * m % n return c print ( potega (2, 1000000, 1000000009)) Przepisz powyższy kod i uruchom w interpreterze repl.it. Okaże się, że program rzeczywiście wylicza pewną wartość i po chwili wypisuje ją na ekran. Czy ten kod działa szybko? Po naciśnięciu przycisku run, wynik działania pojawia się na ekranie po chwili. Co jeśli zwiększymy e dziesięciokrotnie? Uruchom program ponownie, zmieniając wartość zmiennej e na 10 000 000. Tym razem program powinien wykonywać się już kilka sekund. Dla e = 100 000 000 czas działania wynosiłby już kilkadziesiąt sekund. Tak jak wspominałem, w algorytmie RSA używa się wykładników które mają setki cyfr, a nasz program działa wolno już dla wykładnika, który ma raptem 9 cyfr. Widzimy zatem, że w praktyce trzeba potrafić potęgować szybciej. 1.1 Pomiar czasu Żeby wygodniej mówiło nam się o szybkości działania programu, przyda nam się narzędzie do pomiaru czasu. Do mierzenia czasu działania programu w Pythonie służy polecenie timeit. Przykład użycia: from timeit import timeit def potega (m, e, n): c = 1 for i in range(e): c = c * m % n return c print ( timeit ( potega (2, 1000000, 1000000009), globals = globals (), number = 1)) 1

Aby użyć polecenia timeit, należy najpierw zaimportować odpowiedni pakiet, a następnie wywołać polecenie timeit z odpowiednimi argumentami. Pierwszym argumentem jest string (otoczony apostrofami) zawierający polecenie do wykonania. Parametr globals = globals() jest niezbędny, aby używać zdefiniowanych wcześniej funkcji (w naszym przypadku, funkcji potega). Parametr number jest przydatny w przypadku bardzo szybkich fragmentów kodu wówczas czas działania samego fragmentu jest porównywalny z błędem pomiaru wynikającym z czasem samego uruchomienia programu. Dlatego, aby uzyskać lepszą dokładność, możemy np. mierzyć czas tysiąckrotnego powtórzenia wykonania danego fragmentu kodu. My jednak na razie będziemy mierzyć pojedynczy przebieg programu, dlatego ustawimy number = 1 (gdybyśmy nie napisali tego wprost, Python przyjąłby domyślną wartość number = 1000000). Uruchom powyższy program (dla e = 1 000 000) na repl.it i sprawdź, jak szybko działa ten kod. Interpreter powinien wypisać liczbę ok. 0.1, co oznacza, że program działa ok. 0.1 sekundy. Możesz uruchomić program kilkukrotnie okaże się, że czas działania za każdym razem będzie nieco inny, ale różnice nie będą znaczące. Ile operacji wykonuje ten kod? Główną częścią programu jest pętla, w której program e razy wykonuje działanie mnożenia (*) i dzielenia modulo (%). Zatem dla e = 1 000 000 program wykonuje ok. 2 miliony operacji arytmetycznych i dzieje się to w czasie ok. 0.1 s. W ten sposób udało nam się oszacować szybkość Pythona! A dokładniej szybkość interpretera Pythona udostępnionego na stronie repl.it. Interpreter ten wykonuje ok. 20 mln operacji arytmetycznych na sekundę. 1.2 Złożoność czasowa Zadanie 1 Porównaj czasy działania powyższego kodu dla następujących wartości parametru e: [1000, 10000, 100000, 1000000, 10000000] Okaże się, że każde kolejne wykonanie trwa ok. 10 razy dłużej od poprzedniego. Oznacza to, że czas działania programu jest wprost proporcjonalny do e. W takim przypadku mówimy, że złożoność czasowa tego algorytmu jest liniowa względem e i oznaczamy ją przez O(e). W dalszej części zajęć skonstruujemy szybszy algorytm potęgowania, czyli algorytm o mniejszej złożoności. 2 Rekurencja Funkcja rekurencyjna to funkcja, która wywołuje samą siebie. Najprostszym przykładem funkcji rekurencyjnej jest funkcja obliczającą silnię. Silnią liczby n nazywamy iloczyn wszystkich liczb od 1 do n i oznaczamy n! = 1 2 3... n. Rekurencyjna funkcja obliczająca silnię opiera się na rekurencyjnym wzorze na silnię, czyli wzorze, który do obliczenia silni liczby n korzysta z wartości silni obliczonych dla mniejszych liczb: n! = n (n 1)! Aby wywołanie funkcje rekurencyjnej miało szansę kiedyś się zakończyć, funkcja musi posiadać warunek brzegowy. W przypadku silni, warunkiem tym jest równość 0! = 1 Powyższe wzory prowadzą nas do następującej funkcji obliczającej silnię liczby n: def silnia (n): i f n == 0: return 1 e l s e : return n * silnia ( n - 1) Przepisz powyższy kod do interpretera repl.it i oblicz silnię liczby 10. 2

Zadanie 2 Napisz funkcję rekurencyjną fib(n), która obliczy n-tą liczbę Fibonnacci ego zgodnie z następującymi wzorami: Ile wynosi f 10? 2.1 Szybkie potęgowanie f 1 = 1 f 2 = 1 f n = f n 1 + f n 2 Stosując standardowy algorytm potęgowania, aby np. obliczyć 2 16 musimy wykonać ok. 16 mnożeń 1. Da się jednak tę samą liczbę obliczyć szybciej: 1. Wykonajmy pierwsze mnożenie: 2 2 = 4. Po wykonaniu tego mnożenia wiemy, że 2 2 = 4. 2. Wiemy skądinąd, że 2 2 2 2 = 2 4. Zatem wykonując drugie mnożenie otrzymujemy: 2 4 = 4 4 = 16. 3. Analogicznie, po trzecim mnożeniu uzyskamy 2 8 = 16 16 = 256. 4. Ostatecznie, po czwartym mnożeniu obliczymy szukaną wartość: 2 16 = 256 256 = 65536. Tym sposobem obliczyliśmy liczbę 2 16 wykonując 4 zamiast 15 mnożeń. Czy to duża różnica? W tym przypadku niewielka. Ale gdybyśmy podnosili jakąś liczbę do potęgi e = 2 10 = 1024, w pierwszym sposobie musielibyśmy wykonać 1023 operacji mnożenia, a w drugim tylko 10. Dla e = 2 20 = 1048576 pierwszy sposób wymaga 1048575 operacji mnożenia, a drugi jedynie 20. Widzimy, że im większe e, tym różnica pomiędzy algorytmami robi się bardziej znacząca. Oczywiście przedstawiony powyżej algorytm zadziała jedynie w przypadku, gdy wykładnik jest potęgą dwójki, np. przy obliczaniu 17 64, ale już nie przy obliczaniu 17 59. Ogólny algorytm szybkiego potęgowania działający dla dowolnej potęgi jest implementacją następujących wzorów rekurencyjnych, na potęgowanie: m 0 = 1 m e = m e/2 m e/2 m e = m m e 1 Algorytm ten zaimplementowany w Pythonie wygląda tak: def potega2 (m, e, n): i f e == 0: return 1 e l i f e % 2 == 0: pom = potega2 (m, e / 2, n) return ( pom * pom ) % n e l s e : return (m * potega2 (m, e - 1, n)) % n Jest to algorytm rekurencyjny i jego działanie można odczytać następująco: Warunek brzegowy: jeśli doszedłeś do e = 0, to zwróć 1 (bo m 0 = 1). Jeśli e jest parzyste, to najpierw rekurencyjnie oblicz m e/2, a następnie zwróć jako wynik m e = m e/2 m e/2. Jeśli e jest nieparzyste, to najpierw rekurencyjnie oblicz m e 1, a następnie zwróć jako wynik m e = m m e 1. 1 Dokładniej, wystarczy 15 mnożeń: 2 16 = 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2. 3

Dobrym zobrazowaniem działania algorytmu jest rozwinięcie jego kolejnych rekurencyjnych wywołań na przykładzie obliczania liczby 17 59 : 17 59 = 17 17 58 = 17 (17 29 17 29 ) 17 29 = 17 17 28 = 17 (17 14 17 14 ) 17 14 = 17 7 17 7 17 7 = 17 17 6 = 17 (17 3 17 3 ) 17 3 = 17 17 2 = 17 (17 1 17 1 ) 17 1 = 17 17 0 = 17 1 Jeżeli powyższe mnożenia będziemy wykonywali od końca, będziemy potrzebowali łącznie 10 operacji mnożenia, aby uzyskać wartość 17 59 : 17 1 = 17 1 17 2 = 17 1 17 1 17 3 = 17 17 2 17 6 = 17 3 17 3 17 7 = 17 17 6 17 14 = 17 7 17 7 17 28 = 17 14 17 14 17 29 = 17 17 28 17 58 = 17 29 17 29 17 59 = 17 17 58 Zadanie 3 Zmierz czas działania powyższej procedury dla m = 2, n = 1000000009 i następujących wartości e: [1000, 10000, 100000, 1000000, 10000000] Porównaj czasy działania z czasami działania funkcji potega, którą zaimplementowaliśmy na początku zajęć. 2.2 Złożoność algorytmu Jeżeli poprawnie rozwiązałeś poprzednie zadanie, powinieneś otrzymać czasy rzędu 10 5 sekundy 2. To bardzo dobry wynik! Algorytm ten działa ponad tysiąckrotnie lepiej niż algorytm, który stworzyliśmy na początku zajęć. Spróbujmy teraz dokładniej oszacować złożoność programu. Jeżeli przyjrzymy się otrzymanym wynikom czasowym, zauważymy, że wyniki dla poszczególnych wartości e są zbliżone. Może się nawet zdarzyć, że czas działania dla większego e będzie mniejszy niż analogiczny czas dla mniejszego e. Dzieje się tak, ponieważ obliczone czasy działania są porównywalne z błędem pomiarowym. Aby otrzymać dokładniejsze wyniki, będziemy mierzyć czasy tysiąckrotnego wykonania poszczególnych funkcji. W tym celu w poleceniu timeit zmień wartość parametru number na 1000 i jeszcze raz oblicz wszystkie czasy. Po tej zmianie powinieneś otrzymać czasy ok. 0.01 s. Funkcja potega, którą napisaliśmy na początku zajęć, ma złożoność liniową względem e, tzn. jej czas wywołania jest wprost proporcjonalny do e. Czy tak samo jest w przypadku funkcji potega2? Nie! Największa wartość e jest 10 000 razy większa od najmniejszej, a czas działania dla największej wartości e jest tylko kilkukrotnie większy od czasu działania dla najmniejszej wartości e. Oznacza to, że algorytm potega2 ma złożoność mniejszą niż liniowa względem e. Jaką dokładnie złożoność ma nasz algorytm? Aby się o tym przekonać zmierz czasy działania funkcji dla następujących wartości e: (** w Pythonie oznacza podnoszenie do potęgi) 2 Zapis 1.26e-05 oznacza 1.26 10 5 4

[10**10, 10**20, 10**40, 10**80, 10**160] Tym razem powinniśmy otrzymać ciąg wartości, z których każda kolejna jest ok. dwukrotnie większa od poprzedniej. Oznacza to, że czas działania programu jest proporcjonalny do wykładnika, do którego podnosimy liczbę 10, aby uzyskać liczbę e. Innymi słowy, czas działania programu jest proporcjonalny do logarytmu z e. W takim przypadku mówimy, że złożoność algorytmu wynosi O(log e). 2.3 Dygresja o logarytmach Logarytm jest funkcją odwrotną do potęgowania. Jeśli a b = c, to b = log a c. Np. log 10 100 = 2, log 2 32 = 5, log 10 (10 160 ) = 160. Dlatego, żeby mówić o logarytmie, trzeba podać jego podstawę (liczbę w indeksie dolnym). Powszechnie używana jest notacja log x (bez podstawy), jednak nie ma zgodności, co taki napis oznacza. Chemicy lubią używać takiej notacji do logarytmów o podstawie 10, fizycy do logarytmów o podstawie e = 2, 718281... W informatyce najczęściej używamy logarytmów o podstawie 2. Na szczęście w przypadku badania złożoności i notacji O(...) nie ma znaczenia, którego logarytmu użyjemy, ponieważ każdy logarytm jest proporcjonalny do logarytmu o podstawie 2. Na przykład: log 10 1000 = log 2 1000 log 2 10 0.301 log 2 1000 Skoro notacja O(x) oznacza, że czas działania jest proporcjonalny do x, to algorytm o złożoności O(log n) będzie miał czas działania proporcjonalny do logarytmu z n o dowolnej podstawie. To usprawiedliwia nas do pisania O(log e) zamiast np. O(log 10 e). 2.4 Dygresja o potęgowaniu Potęgowanie modulo jest na tyle przydatną funkcją, że zostało w efektywny sposób zaimplementowane w Pythonie. Oprócz znanej Wam już notacji a**b, do obliczania a b w Pythonie istnieje również funkcja pow(a,b). Funkcja ta może przyjąć trzeci, opcjonalny parametr: pow(a, b, n) i wówczas oblicza a b (mod n). 3 Największy wspólny dzielnik Jak wiecie z wykładu, do obliczania największego wspólnego dzielnika dwóch liczb służy algorytm Euklidesa. Algorytm ten również opiera się na rekurencyjnej zależności: NW D(a, b) = NW D(b, a mod b) Warunkiem brzegowym jest w tym przypadku warunek NW D(a, 0) = a Zadanie 4 Napisz funkcję NWD(a, b), która obliczy największy wspólny dzielnik liczb a i b. Oblicz największy wspólny dzielnik liczb 675675 i 272272. 4 Zadania dodatkowe Zadanie 5 Spróbuj oszacować złożoność algorytmu Euklidesa. Wiedząc, że interpreter Pythona ze strony repl. it wykonuje ok. 20 mln operacji na sekundy, jaki jest rząd wielkości liczb a i b, dla których wywołanie funkcji NWD(a, b) się wykona w czasie nieprzekraczającym kilku sekund? 5

Zadanie 6 Ciągi (a n ), (b n ) i (c n ) zdefiniowane są następującymi wzorami: a 1 = 1 b 1 = 1 a n = 2b n 1 b n = a n 1 + 1 c n = a n + b n Oblicz wartość c 17. Wskazówka: do obliczania wyrazów każdego z ciągów stwórz oddzielną funkcję rekurencyjną. Zadanie 7 Napisz funkcję euklides(p, q), która obliczy liczby całkowite a i b, takie że zachodzi równość a p + b q = 1 Skorzystaj z rozszerzonego algorytmu Euklidesa prezentowanego na wykładzie tydzień temu. Zadanie 8 Napisz funkcję odwroc(x, n), która obliczy odwrotność liczby x modulo n, czyli taką liczbę y, że zachodzi kongruencja: x y 1 (mod n) Wykorzystaj funkcję euklides z poprzedniego zadania i metodę prezentowaną na wykładzie tydzień temu. 5 Praca domowa nr 6 Rozwiązania zadań należy przesłać do czwartku 18 maja do godz. 16 59 na adres licealisci.pracownia@ icm.edu.pl wpisując jako temat wiadomości Lx PD6, gdzie x to numer grupy, np. L3 PD6 dla grupy L3, itd. Zadanie domowe 1 (1 pkt) Napisz funkcje szyfruj_rsa(n, e, m), która szyfruje liczbę m algorytmem RSA przy użyciu klucza publicznego e i danego n. Zaszyfruj liczbę 13 przy użyciu klucza e = 541 i n = 2501. Zadanie domowe 2 (2 pkt) Napisz funkcję rozloz(n), która rozkłada liczbę n na czynniki, tj. wypisuje liczbę n przedstawioną jako iloczyn dwóch liczb większych od 1 lub wypisuje stosowny komunikat, jeśli liczba jest pierwsza. Przykład wywołania funkcji: rozloz (245) rozloz (541) 245 = 5 * 49 Liczba 541 jest pierwsza. Zadanie domowe 3 (3 pkt) Rozważmy algorytm RSA z n = 1 000 009 i kluczem publicznym e = 37. Korzystając z tych informacji, znajdź wartość klucza prywatnego d. Wskazówka: Przedstaw liczbę n jako iloczyn dwóch liczb pierwszych p q i skorzystaj z faktu, że w algorytmie RSA liczby e i d spełniają zależność: e d 1 (mod (p 1) (q 1)) Różnych możliwych wartości d jest stosunkowo niewiele (możemy ograniczyć się do wartości mniejszych od n). Możemy więc sprawdzić wszystkie możliwe wartości d i wybrać tę, która spełnia powyższą kongruencję. Sprawdź swoje rozwiązanie szyfrując liczbę 13 kluczem e = 37, a następnie rozszyfrowując obliczonym kluczem d. 6