Największy wspólny dzielnik dwóch liczb naturalnych ALGORYTM EUKLIDESA Algorytm opisany w Księdze VII Elementów Euklidesa z III wieku p.n.e. pozwala szybko znaleźć nwd(a,b) - największy wspólny dzielnik dwóch liczb naturalnych a i b. a dla a = b nwd( a, b) = nwd( a b, b ) dla a > b lub nwd( a, b a) dla a < b dopóki a róŝne od b jeŝeli a>b to a=a-b w przeciwnym razie b=b-a zwróć a Ten algorytm nie jest optymalny, zwłaszcza gdy a i b róŝnią się znacznie, np. a = 10 10 i b = 10. A oto szybszy alogorytm, który zamiast odejmowania wykorzystuje operację dzielenia modulo : a dla b = 0 nwd ( a, b) = nwd( b, a mod b ) dla b >= 1 gdzie: mod operator dzielenia modulo; wynikiem jego działania jest reszta z dzielenia a przez b, na przykład 19 mod 7 = 5 Przykład: nwd(16,12) = nwd(12,4) = nwd(4,0) = 4 - funkcja nwd jest wywoływana 3 razy: Napisz ciąg wywołań funkcji rekurencyjnej nwd(a,b) i podaj ile razy wywołano funkcję nwd : a) dla danych a=56 i b=72 Odp: 4 wywołania, nwd=8 b) dla danych a=76 i b=56 Odp: 5 wywołań, nwd=4 c) Podaj iteracyjny przepis funkcji nwd(a,b) z dzieleniem modulo: Odp: dopóki (b>0) r a mod b a b b r zwróć a Istnieje jeszcze szybsza wersja algorytmu Euklidesa: binary GCD, która zamiast zwykłych operacji dzielenia wykorzystuje przesunięcia bitowe przy dzieleniu i mnoŝeniu przez 2: 1. jeŝeli a i b są parzyste, to NWD(a, b) = 2*NWD( a / 2, b / 2 ) 2. dopóki b>0 powtarzaj jeŝeli a jest parzysta i b jest nieparzysta, to NWD(a, b) = NWD( a / 2, b) zaś jeŝeli a jest nieparzyste a b jest parzyste, to NWD(a, b) = NWD(a, b / 2 ) jeŝeli a i b są nieparzyste, to gdy a>b to NWD(a, b) = NWD( (a b) / 2, b) zaś gdy a<b, to NWD(a, b) = NWD(a,( (b-a) / 2 ). 3. zwróć a*2 k, gdzie k jest liczbą wspólnych czynników 2 wyeliminowanych w kroku 1.
ROZSZERZONY ALGORYTM EUKLIDESA Największy wspólny dzielnik moŝna przedstawić w postaci zaleŝności liniowej od a i b: nwd(a,b) = x*a + y*b gdzie x i y są współczynnikami o wartościach całkowitych (niekoniecznie dodatnich) Przykład: nwd(45,35) = 5 = -3*45 + 4*35 tu: x=-3 i y=4 nwd(45,35) = nwd(35,10) = nwd(10,5) = nwd(5,0) = 5 ZauwaŜ: 45 35 = 1 reszty 10 tzn. 10 = 45 1*35 oraz = 3 reszty 5 tzn. 5 = 35 3* 10 35 10 Zbierzmy to razem od końca: 5 = 35 3*10 = 35 3*(45-1*35) = 35 3*45 + 3*35 = -3*45 + 4*35 Rozszerzony algorytm Euklidesa pozwala łatwo znaleźć nie tylko nwd(a,b), ale takŝe współczynniki x,y zaleŝności liniowej x*a+y*b dzieki temu, Ŝe przechowuje w kolejnych iteracjach nie tylko resztę z dzielenia: r = a mod b, ale takŝe wynik dzielenia całkowitego: iloraz q = a div b gdzie div operator dzielenia cakowitego. Współczynniki x, y w i-tej iteracji Rozrzeszonego Algorytmu Euklidesa opisane są zaleŝnościami: xi = xi yi = yi 2 2 q * x q * y i 1 x, y przyjmują zawsze wartości początkowe (dla iteracji i=0): i 1 krok (-2): x 2 = 1 y 2 = 0 krok (-1): x y 1 1 = 0 1 = Ostateczne wartości x,y otrzymujemy w tym kroku iteracji, który zawiera ostatnią niezerową wartość reszty z dzielenia a mod b. Przykład dla a=45 i b=35 i a=45 b=35 r = a mod b q = a div b x = x i-2 q*x i-1 y = y i-2 q*y i-1-2 - - - - 1 0-1 - - - - 0 1 0 45 35 10 1 1 1*0 = 1 0 1*1 = -1 1 35 10 5 3 0 3*1 = -3 to jest wynik x 2 10 5 0 - - - 3 5 to jest wynik nwd 0 - - - - ZaleŜność liniowa: nwd(45,35) =..5.. = -3.. *45 +.4.. * 35 nwd x a y b 1 3*(-1) = 4 to jest wynik y
Zadanie: znajdź nwd(a,b) i jego zaleŝność liniową od a,b dla danych: a=20 i b=13 Uzupełnij zacienione komórki w tabeli i formułę znajdującą się poniŝej tabeli: a b r = a mod b q = a div b x = x i-2 q*x i-1 y = y i-2 q*y i-1 - - - - 1 0 - - - - 0 1 20 13 tu jest wynik x tu jest wynik y 0 - - - tu wynik nwd 0 - - - - Twoja zaleŝność liniowa: nwd(20,13) =.... =.. *20 +... * 13 Odp: nwd(20,13) = 1 = 2*20 3*13 Zadanie : Napisz i uruchom program, który pobiera dwie liczby naturalne a,b a następnie oblicza i zwraca wartość nwd(a,b) oraz wartości współczynników x,y zaleŝności nwd(a,b)=x*a + y*b. czytaj a,b; x2 1 x1 0 y2 0 y1 1 dopóki (b>0) r a mod b q a div b jeŝeli r>0 a b x x2 q*x1 y y2 q*y1 x2 x1 x1 x y2 y1 y1 y b r pisz nwd= a x= x1 y= y1 czerwoną czcionką napisano to, co stanowi rozszerzenie zwykłego algorytmu Euklidesa
Zastosowania Rozszerzonego Algorytmu Euklidesa I. Lanie wody Równanie diofantyczne liniowe: x*a + y*b = k gdzie dane są liczby całkowite: a,b,k, jest to równanie, którego rozwiązania: x,y szuka się w dziedzinie liczb całkowitych. Rozwiązanie istnieje tylko wtedy, gdy największy wspólny dzielnik nwd(a,b) jest także dzielnikiem liczby k. Problem można interpretować następująco: Masz dużą kadź oraz dwa mniejsze naczynia o pojemnościach a i b. Aby umieścić w kadzi dokładnie k litrów wody za pomocą naczyń a i b,odmierzysz wodę x razy naczyniem o pojemności a, oraz y razy drugim naczyniem, o pojemności b. Kadź uda się napełnić żądaną objętością k litrów wody przy pomocy naczyń a,b tylko wówczas, gdy nwd(a,b) jest także podzielnikiem liczby k. Przykład: 1) dla danych: a=5, b=7, k=3 istnieje rozwiązanie: x=2, y=-1 2) dla danych: a=4, b=10, k=15 nie ma rozwiązania. Zwykły algorytm Euklidesa pomoże rozstrzygnąć problem: czy w ogóle uda się odmierzyć k litrów naczyniami a i b. Jeżeli tak to rozszerzony algorytm Euklidesa dostarczy informacji o tym ile razy trzeba użyć naczynia a i ile razy użyć naczynia b, rozwiazaniem będą (współczynniki x,y). Ale często nie będzie to rozwiązanie optymalne z punktu widzenia praktycznego. Chciałoby się jak najmniej razy machać tymi naczyniami, to znaczy: aby liczba operacji nalewania i wylewania wody: x + y była minimalna. Przykład: odmierz 9 litrów przy pomocy naczyń: 12 i 21 litrowych. Rozszerzony algorytm Euklidesa zaproponuje: 6*12 + (-3)*21 = 9, razem 6+3=9 operacji ale przecież można prościej: 1*21 + (-1)*12 = 9, wystarczą 2 operacje. Pozostaje także problem jak duża musi być kadź. Zauważ że jeden ze współczynników (x,y) może być ujemny, to znaczy musisz zabrać z kadzi a lub b litrów wody uprzednio nalanej. Kadź musi więc być odpowiednio duża, często większa od żądanej objętości k. Jaka jest potrzebna minimalna pojemność kadzi? Zadanie: W jaki sposób napełnić kadź dwoma litrami wody k=2, posługując się naczyniami o pojemnościach a=5 i b=7 litrów tak, aby w trakcie napełniania kadzi ilość zawartej w niej wody była jak najmniejsza? Jaką co najmniej pojemność musi mieć kadź?
// program: napełnianie kadzi int A,B,k; // A,B naczynia, k - kadź cout<<"ile nalac? "; cin>>k; cout<<"jakie naczynia a,b? "; cin>>a>>b; int a=a, b=b; int x, x1=0, x2=1; int y, y1=1, y2=0; int r,q; while (b>0) { r=a%b; q=a/b; if (r>0) { x=x2-q*x1; y=y2-q*y1; x2=x1; x1=x; y2=y1; y1=y; a=b; b=r; cout<<"nwd="<<a<<"=x*a+y*b="<<x<<"*"<<a<<" + "<<y<<"*"<<b <<endl<<endl; if (k%a!=0) cout<<"nie da się nalać "<<k<<" litrow tymi naczyniami\n"; else { cout<<"\n napelnic kadz mozna tak:\n"; int q=k/a; cout<<a<<"*"<<x*q<<" + "<<B<<"*"<<y*q<<endl; cout<<"\n ale moŝna i tak:\n"; int w=0; // bieŝący stan napełnienia kadzi while (w!=k) { if (w<k) { // jeŝeli w kadzi jest za mało if (k-w ==A) {w=w+a; cout<<" wlej "<<A<<" w="<<w<<endl; else if (k-w ==B) {w=w+b; cout<<" wlej "<<B<<" w="<<w<<endl; else { if (A>B) {w=w+a; cout<<" wlej "<<A<<" w="<<w<<endl; else {w=w+b; cout<<" wlej "<<B<<" w="<<w<<endl; else { // jeŝeli w kadzi jest za duŝo if (k-w ==-A) {w=w-a; cout<<" odlej "<<A<<" w="<<w<<endl; else if (k-w ==-B) {w=w-b; cout<<" odlej "<<B<<" w="<<w<<endl; else { if (A<B) {w=w-a; cout<<" odlej "<<A<<" w="<<w<<endl; else {w=w-b; cout<<" odlej "<<B<<" w="<<w<<endl;
II. Rozszerzony Algorytm Euklidesa przydaje się w kryptografii z zastosowaniem algorytmu RSA, do wyznaczenia klucza prywatnego gdy znamy składniki klucza publicznego. Kodowanie RSA: szyfrowanie: ( dana e ) mod n gdzie (n,e) klucz publiczny deszyfrowanie: ( szyfr d ) mod n Najpierw wybiera się klucz publiczny (n,e), jawny, służący do szyfrowania: gdzie (n,d) klucz prywatny Wybieramy dwie liczby pierwsze, na przykład: p = 7 i q = 11. Ich iloczyn: n = p*q = 77 Wyznaczamy Φ =(p -1)(q -1) = 6 *10 = 60. Wybieramy liczbę e względnie pierwszą z Φ, czyli taką że nwd(φ,e)=1, na przykład e = 13. Klucz publiczny: (77,13) Następnie wyznacza się klucz prywatny (n,d), tajny, służący do odszyfrowania: Szukamy d takiego, Ŝe d*e 1 (mod Φ) czyli w naszym przykładzie: d * 13 1 (mod 60) Rozszerzony Algorytm Euklidesa NWD(Φ,e) zwróci nam parę współczynników (x,y). Jeśli argumenty NWD są liczbami wzglednie pierwszymi, to (x,y) są są odwrotnościami modularnymi argumentów funkcji NWD(Φ,e) : Odwrotność modulo: x jest odwrotnością modulo b liczby a, NWD(Φ,e) = 1 = x*φ + y*e to znaczy: (x*a) mod b =1, Liczby Φ, e są względem siebie pierwsze, więc w innym zapisie x * a 1(mod b) y*e 1 (mod Φ), co oznacza Ŝe znaleziona wartość y odpowiada d = y Φ e q=φ /e Φ =e e =Φ mod e x -2 x -1 = x i-2 q*x i-1 y -2 y -1 = y i-2 q*y i-1 60 13-60 13 1 0 0 1 4 13 8 0 1 1-4 1 8 5 1-1 -4 5 1 5 3-1 2 5-9 1 3 2 2-3 -9 14 1 2 1-3 5 14-23 2 1 0 5-23 Sprawdźmy: nwd(φ,e) = x*φ + y*e = 5*60 + (-23)*13 = 1 Współczynnik y ma wartość ujemną. RównowaŜne mu będą wszystkie wartości róŝniące się o Φ=60, względem którego liczymy modulo: 13 * (-23) 1 mod 60 13 * (60-23) 1 mod 60 13 * 37 1 mod 60 czyli d = 37. Klucz prywatny (77,37). Podanym kluczem moŝna szyfrować liczby o wartości mniejszej od n=77.
NaleŜy utajnić wszelką informację o uŝytych liczbach p i q : 7 i 11, poniewaŝ znając te liczby łatwo moŝna wyznaczyć klucz prywatny. Cała trudność złamania szyfru RSA opiera się na trudności rozkładu liczby n na iloczyn liczb pierwszych p i q w realnym czasie (gdy wartość n jest dostatecznie duŝa) Zadanie : Dany jest klucz publiczny (e,n): n=91 oraz e=5. Wyznacz brakującą liczbę d w kluczu prywatnym: wyznacz dwie liczby pierwsze : p i q takie Ŝe n=p*q Rozwiązanie - na przykład: p=13 i q=7) dobierz liczbę d wiedząc Ŝe d jest odwrotnością modulo Φ liczby e, czyli d * e 1 (mod Φ) Wskazówka: zastosuj Rozszerzony Algorytm Euklidesa dla nwd(φ,e) i przypisz d y Rozwiązanie: Φ=(p-1)*(q-1)= (13-1)*(7-1)=12*6=72 Φ e r = Φ mod e q = Φ div e y = y i-2 q*y i-1 - - - - 0 - - - - 1 72 5 2 14-14 5 2 1 2 2 1 0 - - 29 to jest wynik d Rozwiazanie: Klucz prywatny (d,n) = (29,91), wartość liczby deszyfrujacej d=29 Podczas szyfrowania RSA natrafiamy na problem bardzo dużych liczb jako wyników potęgowania. Można go uniknąć, bo potrzebna jest tylko potęga modulo n, co wcale nie musi być dużą liczbą. h = a e mod n można rozpisać jako h = ((a mod n) * a mod n)... * a mod n I jeszcze usprawnić algorytm. Pamiętasz sprytny algorytm szybkiego potęgowania (dla wykładników naturalnych)? Wykorzystywał on właściwość b a = ( a funkcja potęga(a, b) // wersja rekurencyjna jeŝeli b = 0 zwróć 1 jeŝeli b jest nieparzysta zwróć a*potęga(a, n-1) w przeciwnym przypadku a = potęga(a, b/2) zwróć a 2 z potęgowaniem modulo jest podobnie: b 2 ) 2 funkcja potegamodulo (a, b, n ) // wersja iteracyjna wynik =1; dopóki (b>0) { jeŝeli (b nieparzyste) wynik = (wynik*a) mod n; a = (a*a) mod n; b = b/2; zwróć wynik; funkcja potega(a,b) // wersja iteracyjna: wynik=1; x=a; dopóki (b>0) jeŝeli (b nieparzyste) wynik *= x; b -- w przeciwnym razie x *= x b = b/2 zwróć wynik
Zadanie Zastosuj klucz publiczny z poprzedniego zadania do zaszyfrowania słowa DEMOTYWATOR. Zaszyfruj osobno każdy znak tego słowa (biorąc jako daną źródłową kod ASCII znaku). Wyświetl (lub zapisz w pliku) ciąg liczb będących wynikiem szyfrowania. Nastepnie zdeszyfruj otrzymany ciąg liczb przy pomocy klucza prywatnego z poprzedniego zadania. Kolejne zdeszyfrowane liczby potraktuj jako kody ASCII i wyświetl ich reprezentację znakową. Mam nadzieję że otrzymasz z powrotem słowo DEMOTYWATOR int potegamodulo(int a, int b, int c) { int r=1; while (b>0) { if (b%2!= 0) r=(r*a)%c; a=(a*a)%c; b=b/2; return r; int main() { string s="demotywator"; int ns=s.length(); int n=77; // nie moŝesz szyfrować liczb większych od n int e=13; // klucz publiczny (77,13) int d=37; // klucz prywatny (77,37) int T[20]; // tablica przechowuje liczby po zaszyfrowaniu for (int i=0; i<ns; i++) { ponieważ najmniejszy kod (litera A) ma wartość 65, odejmuję 64 od int kod = (int)s[i]-64; wszystkich kodów aby uzyskać mniejsze liczby do szyfrowania // szyfrowanie: (kod^e) % n int zasz = potegamodulo(kod,e,n); cout<<s[i]<<" "<<kod<<" "<<zasz<<" "<<endl; T[i]=zasz; cout<<endl<<endl; for (int i=0; i<ns; i++) { int z = T[i]; // deszyfrowanie: (z^d) % n int odsz = potegamodulo(z,d,n); char znak = (char)(odsz+64); cout<<znak<<" "<<odsz<<endl; cout<<endl<<endl; system("pause"); return EXIT_SUCCESS;