Podstawa wykładowa: Część 4 - slajdy 5, 13, 15: problem logarytmu dyskretnego (DLP), równanie g^x = h mod p, algorytm Baby-step Giant-step Shanka, kompromis
czasowo-pamięciowy O(sqrt(p)).
Cel
Celem zadania jest praktyczna implementacja algorytmu Baby-step Giant-step (BSGS) Daniela Shanka w języku Python do rozwiązywania problemu logarytmu dyskretnego (DLP). Student
implementuje kryptoanalityczny atak na protokół Diffiego-Hellmana w grupach zredukowanych, wyznaczając tajny wykładnik x z podsłuchanego klucza publicznego h = g^x mod p przy złożoności
czasowo-pamięciowej O(sqrt(p)).
Scenariusz – opis
Trudność odwrócenia potęgowania modularnego w ciele liczb pierwszych, czyli wyznaczenie x z równania g^x = h (mod p), stanowi fundament protokołu DH i kryptosystemu ElGamala.
Naiwny atak siłowy (brute-force) wymaga sprawdzenia do p-1 wartości, co dla dużych liczb pierwszych jest niewykonalne. W 1971 roku Daniel Shanks zaprojektował algorytm Baby-step
Giant-step oparty na kompromisie czasowo-pamięciowym (Time-Memory Trade-Off). Niech m = ceil(sqrt(n)), gdzie n jest rzędem grupy. Tajny wykładnik można przedstawić w postaci x = i*m + j,
gdzie 0
<= i, j < m. Równanie g^x = h przyjmuje postać g^(i*m + j) = h, co po przekształceniu daje g^j = h * (g^(-m))^i (mod p). Napastnik w fazie Baby-step tworzy tablicę par (g^j mod p, j) dla wszystkich 0 <= j < m, a w fazie Giant-step poszukuje kolizji w tablicy dla kolejnych wartości i.
Co ma wykonać student / grupa
W pliku bsgs_dlp_solver.py implementujesz procedurę rozwiązania problemu logarytmu dyskretnego algorytmem Shanka. Konfigurujesz parametry
testowej grupy Diffiego-Hellmana: liczbę pierwszą p o długości 40-48 bitów oraz generator g. Zakres laboratoryjny ograniczasz do 32-36 bitów (40-48 bitów zjada pamięć
JSLinux) i dokładasz tabelę czas x pamięć 24->44 bity z wnioskiem, dlaczego DH
wymaga 2048 bitów (256 w ECC). Alice losuje tajny klucz prywatny x z zakresu [1, p-1] i oblicza swój publiczny klucz h = pow(g, x, p).
Jako napastnik przechwytujesz wartości p, g oraz h i przystępujesz do fazy przygotowawczej ataku. Obliczasz optymalną długość kroku: m = math.isqrt(p) + 1. Faza Baby-step:
tworzysz słownik (hash table) w Pythonie przechowujący wartości g^j mod p jako klucze oraz
j jako wartości dla j od 0 do m-1. Mierzysz zużycie pamięci tablicy i odnotowujesz, że wymaga ona dokładnie m wpisów.
Obliczasz mnożnik wielkiego kroku: wyznaczasz g^(-m) mod p za pomocą odwrotności modularnej pow(g, -m, p) lub pow(pow(g, m, p), -1, p). Faza Giant-step: inicjalizujesz
zmienną cur = h i w pętli dla i od 0 do m sprawdzasz, czy cur znajduje się w słowniku Baby-step. Gdy wystąpi trafienie (kolizja w tablicy haszującej), odczytujesz
powiązaną wartość j. Wyznaczasz rozwiązanie logarytmu dyskretnego: x_recovered = i * m + j.
Sprawdzasz poprawność matematyczną: weryfikujesz tożsamość pow(g, x_recovered, p) == h. Potwierdzasz, że odzyskane x_recovered jest dokładnie równe tajnemu kluczowi
prywatnemu Alice x. Porównujesz czas wykonania algorytmu BSGS z naiwnym przeszukaniem liniowym, rejestrując setki tysięcy razy wyższą prędkość. Sporządzasz tabelę zależności czasu
wykonania i zużycia pamięci od długości bitowej p (od 24 do 44 bitów). Formułujesz podsumowanie wyjaśniające, dlaczego współczesne standardy DH wymagają grup o rozmiarze co najmniej
2048 bitów (lub 256 bitów w ECC).
Przykładowe kroki do wykonania jako sugestia
- Utwórz plik
bsgs_dlp_solver.py w środowisku roboczym.
-
Zaimplementuj generator parametrów małej grupy
DH (p,
g) o zadanym rozmiarze bitowym.
- Wygeneruj
40-bitową liczbę pierwszą p oraz generator g.
-
Uwaga laboratoryjna: docelowo testuj na
32-36 bitach (większe zjadają pamięć tablicy
BSGS w JSLinux); sporządź tabelę czas x pamięć 24->44 bity.
-
Wylosuj tajny klucz prywatny x i oblicz klucz publiczny
h =
pow(g, x, p).
-
Zaimplementuj funkcję
shanks_bsgs(g, h,
p) rozwiązującą równanie g^x = h
mod p.
-
Oblicz
m = math.isqrt(p) + 1.
-
Zbuduj tablicę Baby
-step: baby_steps
= {pow(g, j,
p): j for j in
range(m)}.
-
Oblicz czynnik wielkiego kroku:
factor =
pow(pow(g, m, p),
-1, p).
-
Zainicjalizuj
cur = h i rozpocznij pętlę
Giant-step dla i in
range(m).
- Sprawdź czy cur
in baby_steps: jeśli tak, zwróć i*m + baby_steps[cur].
-
Zaktualizuj
cur = (cur *
factor) % p w kolejnych iteracjach.
- Uruchom atak na wygenerowanym kluczu h i zmierz czas trwania fazy Baby i Giant.
-
Zweryfikuj tożsamość odzyskanego klucza: assert
pow(g, x_rec,
p) == h.
-
Wykonaj test porównawczy z przeszukaniem naiwnym brute
-force dla małego p (32 bity).
- Wykonaj zrzuty ekranu konsoli prezentujące zbieżność algorytmu i odzyskany sekret.
- Opracuj raport laboratoryjny z analizą kompromisu czasowo
-pamięciowego.
Kontrola zaliczenia (checklist):
- Złamano DH/DLA atakiem BSGS na wektorze ze startera: tablica baby-steps w RAM, giant-steps do kolizji, odzyskany sekret bez O(p).
- Wykazano różnicę: mała grupa (łamana w O(√p)) vs grupa 2048+ (niewykonalne, ekstrapolacja); dołączono czasy i pamięć.
- Dołączono log ataku (rozmiar tablicy, trafienie) + kod łatki (grupy ≥2048 / krzywe 256-bit, walidacja podgrup).
- Opisano w 3–5 zdaniach kompromis czas–pamięć, granice RAM dla BSGS, rho bezpamięciowe jako następny krok.
Wariant offline (fallback): Praca w pełni offline na wektorach ze startera w lokalnym Pythonie 3 bez sieci; dołącz kod ataku, log odzyskanego klucza/tekstu oraz opis kompromisu bezpieczeństwo–wydajność i wytyczne doboru parametrów.
Sugerowane wnioski i podsumowanie - co student ma osiągnąć
Sprawozdanie z realizacji zadania musi zawierać następujące kluczowe wnioski i osiągnięcia:
- Głębokie zrozumienie algorytmu Baby-step Giant-step Shanka i jego roli w kryptoanalizie DLP.
- Opanowanie techniki kolizyjnej w tablicach haszujących do radykalnego przyspieszenia poszukiwania sekretu.
- Empiryczne potwierdzenie redukcji złożoności obliczeniowej z O(p) do O(sqrt(p)).
- Zrozumienie ograniczeń pamięciowych ataku BSGS i konieczności stosowania algorytmów bezpamięciowych (np. rho Pollarda dla DLP).