SZCZEGÓŁOWY SPIS TREŚCI
SŁOWO WSTĘPNE xv
WPROWADZENIE xvii
Stosowane podejście xviii
Dla kogo jest ta książka xviii Układ książki
Podstawy
Szyfry symetryczne
Szyfry asymetryczne
Zastosowania
Podziękowania
xix
Jak budować szyfry blokowe
Rundy szyfru blokowego
Sieć Feistela
może pójść źle
Szyfry strumieniowe stanowe i oparte na liczniku
źródła
Rodzina funkcji skrótu SHA
Konkurencja ze strony
powtórzeniowe
Funkcje pseudolosowe PRF
funkcje PRF są silniejsze od MAC?
skrótów z kluczem na podstawie
OCB – uwierzytelniony szyfr szybszy niż GCM
Wnętrze OCB
OCB
SIV – najbezpieczniejszy uwierzytelniany szyfr?
Inne źródła
9
TRUDNE PROBLEMY
Trudność obliczeniowa
Pomiar czasu wykonania
Czas wielomianowy a superwielomianowy
Klasy złożoności
Niedeterministyczny czas wielomianowy
Czy rozkład na czynniki jest
Szyfrowanie za pomocą RSA
złamania podręcznikowego szyfrowania
Silne szyfrowanie RSA – OAEP
Podpisywanie za pomocą RSA
Łamanie podpisów podręcznikowego RSA
podpisu PSS
Podpisy ze skrótem pełnodomenowym
Implementacje RSA
Szybki algorytm potęgowania – podnoszenie do kwadratu i mnożenie
Małe wykładniki w celu szybszego działania klucza publicznego
Chińskie twierdzenie o resztach
Co może pójść źle
Atak Bellcore na RSA-CRT
Inne źródła
prywatnych wykładników lub modulo
Funkcja Diffiego–Hellmana
Problemy z protokołami Diffiego–Hellmana
Problem obliczeniowy Diffiego–Hellmana
Problem decyzyjny Diffiego–Hellmana
problemów z Diffiem–Hellmanem
Modele ataku dla protokołów uzgadniania klucza
Wydajność
Protokoły Diffiego–Hellmana
Diffie–Hellman
Diffie–Hellman
MQV (Menezes–Qu–Vanstone)
Przestarzały Diffie–Hellman w TLS
Parametry grupy, które nie są bezpieczne
źródła
jest krzywa eliptyczna?
Krzywe eliptyczne na liczbach całkowitych
i mnożenie punktów
krzywych eliptycznych
klucza Diffiego–Hellmana na krzywych eliptycznych
Szyfrowanie z wykorzystaniem krzywych eliptycznych
Wybór krzywej
Docelowe aplikacje i wymagania
Zestaw protokołów TLS
Rodzina protokołów TLS i SSL – krótka historia
w pigułce
i centra certyfikacji
rekordu
Protokół TLS Handshake
kryptograficzne w TLS 1 .3
Ulepszenia w TLS 1 .3 w porównaniu z TLS 1 .2
Ochrona przed aktualizacją wsteczną
Pojedyncze obustronne uzgadnianie
może pójść źle
w przód
Naruszenie bezpieczeństwa centrum certyfikacji
Naruszenie bezpieczeństwa serwera
bezpieczeństwa klienta
wykładnicze i algorytm Simona
ze strony algorytmu faktoryzacji Shora
Shora rozwiązuje problem rozkładu na czynniki
Algorytm Shora i problem logarytmu dyskretnego
Grovera
Dlaczego tak trudno jest zbudować komputer kwantowy?
algorytmy szyfrowania
oparta na kodach korekcyjnych
Kryptografia oparta na kratach
Kryptografia wielu zmiennych
Kryptografia oparta na funkcjach skrótu
może pójść źle
bezpieczeństwa
Szybko do przodu – co się stanie, jeśli będzie za późno?
BEZPIECZEŃSTWO KRYPTOGRAFICZNE

Kryptograficzne definicje bezpieczeństwa nie są takie same jak te ogólnie stosowane w bezpieczeństwie komputerów. Główna różnica między bezpieczeństwem oprogramowania a bezpieczeństwem kryptograficznym jest taka, że to drugie jest mierzalne. W przeciwieństwie do świata oprogramowania, gdzie aplikacje są zazwyczaj postrzegane jako bezpieczne lub nie, w świecie kryptografii często możliwe jest obliczenie rozmiaru wysiłku potrzebnego do złamania algorytmu kryptograficznego. Ponadto, gdy bezpieczeństwo oprogramowania skupia się na uniemożliwianiu napastnikom nadużywania kodu programu, celem bezpieczeństwa kryptograficznego jest zapewnienie, aby dobrze zdefiniowane problemy były niemożliwe do rozwiązania.
Problemy kryptograficzne obejmują pojęcia matematyczne, jednak nie skomplikowaną matematykę – przynajmniej nie w tej książce. Ten rozdział prowadzi nas przez kilka z tych pojęć bezpieczeństwa i to, jak są one stosowane do rozwiązywania rzeczywistych problemów. W poniższych punktach
omówiono określanie bezpieczeństwa kryptograficznego w taki sposób, że zarówno ma to solidne podstawy teoretyczne, jak i nadaje się do zastosowania w praktyce. Wyjaśniam pojęcia bezpieczeństwa informacyjnego obok bezpieczeństwa obliczeniowego, bezpieczeństwa bitowego obok kosztu pełnego ataku, bezpieczeństwa możliwego do udowodnienia obok bezpieczeństwa heurystycznego oraz generowania kluczy symetrycznych i niesymetrycznych. Rozdział ten kończą rzeczywiste przykłady błędów w mocnej na pozór kryptografii.
Definiowanie niemożliwego
W rozdziale 1 opisano bezpieczeństwo szyfru w odniesieniu do możliwości i celów napastnika, uznając go za bezpieczny, jeśli nie było możliwe osiągnięcie tych celów przez napastnika przy danych możliwościach. Ale co oznacza niemożliwe w tym kontekście?
Dwie rzeczy definiują pojęcie niemożliwości w kryptografii: bezpieczeństwo informacyjne oraz obliczeniowe. Z grubsza biorąc, bezpieczeństwo informacyjne dotyczy teoretycznego braku możliwości, a bezpieczeństwo obliczeniowe odnosi się do praktycznej niemożności. Bezpieczeństwo informacyjne nie mierzy bezpieczeństwa, ponieważ widzi szyfr jako bezpieczny lub nie, bez obszarów pośrednich; dlatego jest bezużyteczne w praktyce, mimo że odgrywa ważną rolę w kryptografii teoretycznej. Bezpieczeństwo obliczeniowe jest bardziej odpowiednią i praktyczną miarą siły szyfru.
Bezpieczeństwo w teorii – bezpieczeństwo informacyjne
Bezpieczeństwo informacyjne nie opiera się na tym, jak trudno jest złamać szyfr, lecz czy w ogóle istnieje taka ewentualność. Szyfr jest bezpieczny informacyjnie tylko wtedy, gdy nie można go złamać, nawet mając nieograniczony czas obliczania i pamięć. Jeśli nawet udany atak na szyfr zająłby biliony lat, taki szyfr nie jest bezpieczny informacyjnie.
Na przykład szyfr z hasłem jednorazowym, przedstawiony w rozdziale 1, jest bezpieczny informacyjnie. Przypomnijmy sobie, że szyfruje on jawny tekst P do postaci szyfrogramu C = P ⊕ K, gdzie K jest losowym łańcuchem bitów, które są niepowtarzalne dla każdego tekstu jawnego. Szyfr ten jest bezpieczny informacyjnie, ponieważ mając szyfrogram i nieograniczony czas na wypróbowanie wszystkich możliwych kluczy K i wyznaczenie odpowiadającego mu tekstu jawnego P, nadal nie byłoby możliwe zidentyfikowanie prawidłowego K, ponieważ jest tyle możliwych P, ile K
Bezpieczeństwo w praktyce – bezpieczeństwo obliczeniowe
W przeciwieństwie do bezpieczeństwa informacyjnego bezpieczeństwo obliczeniowe traktuje szyfr jako bezpieczny, jeśli nie można go złamać w sensownym czasie i za pomocą osiągalnych zasobów, takich jak pamięć, sprzęt, budżet, energia itp. Bezpieczeństwo obliczeniowe jest metodą określania bezpieczeństwa szyfru lub dowolnego algorytmu kryptograficznego.
używanych przez miliony, a obejmujących telefony komórkowe, Wi-Fi oraz inteligentne karty transportu publicznego. Ale to już historia. Na szczęście, mimo że zajęło to 20 lat, wiemy teraz, jak projektować bezpieczne szyfry strumieniowe, i wierzymy, że chronią takie elementy, jak połączenia Bluetooth, mobilne połączenia 4G, połączenia TLS i wiele innych.
W tym rozdziale pokazano, jak działają szyfry strumieniowe, i omówiono dwie główne ich klasy: szyfry stanowe oraz szyfry oparte na liczniku.
Następnie przyjrzymy się szyfrom strumieniowym zorientowanym na oprogramowanie oraz na sprzęt i kilku szyfrom, które nie są bezpieczne (takim jak A5/1 w GSM łączności mobilnej oraz RC4 w TLS) oraz kilku najnowocześniejszym bezpiecznym szyfrom (takim jak Grain-128a dla sprzętu oraz Salsa20 dla oprogramowania).
Jak działają szyfry strumieniowe
Szyfry strumieniowe bardziej przypominają deterministyczne generatory bitów losowych (DRBG) niż dojrzałe generatory liczb pseudolosowych (PRNG), ponieważ podobnie jak DRBG, szyfry strumieniowe są deterministyczne. Ich determinizm pozwala odszyfrowywać je poprzez odtwarzanie bitów pseudolosowych używanych do szyfrowania. Korzystając z PRNG, można by zaszyfrować, lecz nigdy nie odszyfrować – co jest bezpieczne, lecz bezużyteczne.
To, co różni szyfry strumieniowe od generatorów DRBG, to fakt, że te ostatnie przyjmują jedną wartość wejściową, natomiast szyfry strumieniowe – dwie: klucz i wartość jednorazową. Klucz powinien być tajny i ma zazwyczaj 128 lub 256 bitów. Wartość jednorazowa nie musi być wartością tajną, lecz powinna być niepowtarzalna dla każdego klucza i zwykle ma między 64 a 128 bitów.
Szyfry strumieniowe tworzą pseudolosowy strumień bitów nazywany strumieniem klucza (keystream). Aby zaszyfrować tekst, strumień klucza i tekst jawny podlegają działaniu XOR, a na szyfrogramie wykonuje się XOR, aby go odszyfrować. Na rysunku 5.1 pokazano podstawowe szyfrowanie szyfrem strumieniowym, gdzie SC jest algorytmem szyfru strumieniowego, KS jest strumieniem klucza, P jest tekstem jawnym, a C szyfrogramem.
SC K
KS C
Rysunek 5.1. Jak szyfruje szyfr strumieniowy, biorąc tajny klucz K i publiczną wartość jednorazową N
Szyfr strumieniowy oblicza KS = SC(K, N), szyfruje jako C = P ⊕ KS i odszyfrowuje jako P = C ⊕ KS. Funkcje szyfrowania i odszyfrowywania są takie same, ponieważ obie robią to samo, a mianowicie wykonują działanie XOR na bitach oraz strumieniu klucza. Z tego powodu na przykład niektóre biblioteki kryptograficzne dostarczają jedną funkcję encrypt, która służy zarówno do szyfrowania, jak i odszyfrowywania.
Szyfry strumieniowe pozwalają szyfrować komunikat za pomocą klucza K1 i wartości jednorazowej N1, a następnie szyfrują inny komunikat przy użyciu klucza K1 i wartości jednorazowej N2, różniącej się od N1, lub za pomocą
klucza K2, różniącego się od K1 i N1. Nie należy jednak nigdy szyfrować ponownie za pomocą K1 i N1, ponieważ byłoby to użycie tego samego strumienia klucza KS. Mielibyśmy wtedy pierwszy szyfrogram C1 = P1 ⊕ KS, drugi szyfrogram C2 = P2 ⊕ KS, a znając P1, można by określić P2 = C1 ⊕ C2 ⊕ P1.
Uwaga Wartość jednorazowa, w języku angielskiem jest nazywana słowem nonce, co jest skrótem od number used only once (liczba użyta tylko raz). W kontekście szyfrów strumieniowych jest czasami określana jako IV (od initial value, czyli wartość początkowa).
Szyfry strumieniowe stanowe i oparte na liczniku
Z ogólnej perspektywy są dwa typy szyfrów strumieniowych: stanowe i oparte na liczniku. Szyfry strumieniowe stanowe (stateful stream ciphers) mają tajny stan wewnętrzny, który ewoluuje podczas generowania strumienia klucza. Szyfr inicjalizuje stan z klucza i wartość jednorazową, a następnie wywołuje funkcję aktualizacji, aby zaktualizować wartość stanu, i tworzy jeden lub więcej bitów strumienia klucza ze stanu, jak to pokazano na rysunku 5.2. Na przykład słynny RC4 jest szyfrem stanowym.
Inicjacja K N
Aktualizacja Aktualizacja Aktualizacja
Rysunek 5.2. Stanowy szyfr strumieniowy
Szyfry strumieniowe oparte na liczniku (counter-based stream ciphers) tworzą fragmenty strumienia klucza z klucza, wartości jednorazowej oraz wartości licznika, jak to pokazano na rysunku 5.3. Inaczej niż w stanowych szyfrach strumieniowych, takich jak Salsa20, żaden tajny stan nie jest zapamiętywany podczas generowania strumienia klucza.
N, Ctr
N, Ctr + 1
N, Ctr + 2
Rysunek 5.3. Szyfr strumieniowy oparty na liczniku
Trudność obliczeniowa
Problem obliczeniowy to odpowiedź na pytanie za pomocą odpowiednich obliczeń, na przykład „Czy 2017 jest liczbą pierwszą?” lub „Ile liter i znajduje się w słowie niezrozumienie?”. Trudność obliczeniowa to własność problemów obliczeniowych, dla których nie ma algorytmu, który działałby w rozsądnym czasie. Takie problemy określa się też jako problemy trudne obliczeniowo (infractable problems), gdyż często są praktycznie niemożliwe do rozwiązania.
Zadziwiające jest, że trudność obliczeniowa jest niezależna od rodzaju urządzenia używanego do obliczeń – czy jest to procesor ogólnego zastosowania, układ scalony, czy też mechaniczna maszyna Turinga. W istocie jednym z pierwszych odkryć teorii złożoności obliczeniowej jest fakt, że wszystkie modele obliczeniowe są równoważne. Jeśli problem może zostać skutecznie rozwiązany za pomocą jednego urządzenia obliczeniowego, to może też zostać skutecznie rozwiązany za pomocą każdego innego urządzenia przez przeniesienie algorytmu na język innego urządzenia – wyjątek stanowią komputery kwantowe, ale one nie istnieją (jeszcze). Wynika stąd, że nie musimy określać wykorzystywanego urządzenia lub sprzętu, gdy omawiamy trudność obliczeniową; musimy tylko rozmawiać o algorytmach.
Aby wyznaczyć trudność, musimy najpierw znaleźć sposób zmierzenia złożoności algorytmu lub czasu jego wykonania. Podzielimy czasy wykonania na trudne i łatwe.
Pomiar czasu wykonania
Większość programistów zna złożoność obliczeniową (computational complexity), czyli przybliżoną liczbę działań wykonywaną przez algorytm w funkcji wielkości danych na wejściu. Wielkość jest liczona w bitach lub w liczbie elementów pobieranych na wejściu. Jako przykład weźmy algorytm pokazany na listingu 9.1, napisany w pseudokodzie.
Szuka on wartości x, w macierzy złożonej z n elementów i zwraca indeks jego położenia.
search(x, array, n): for i from 1 to n { if (array[i] == x) { return i; } } return 0; }
Listing 9.1. Prosty algorytm wyszukiwania napisany w pseudokodzie o złożoności liniowej względem długości macierzy n. Algorytm zwraca indeks miejsca, w którym w macierzy [1,n] znajduje się x, lub 0, jeśli nie ma go w macierzy
W tym algorytmie używamy pętli do znalezienia określonej wartości x, iterując po macierzy. Przy każdej iteracji przypisujemy zmiennej i kolejną
wartość, zaczynając od 1. Potem sprawdzamy, czy wartość na pozycji i macierzy jest równa wartości x Jeśli tak jest, zwracamy numer pozycji i. W przeciwnym przypadku zwiększamy i i próbujemy kolejną pozycję, aż do osiągnięcia wartości n, czyli długości macierzy, a wtedy zwracamy 0.
Dla tego rodzaju algorytmu obliczamy złożoność jako liczbę iteracji pętli: 1 w najlepszym przypadku (jeśli x jest równy macierzy [1]), n zaś w najgorszym przypadku (jeśli z jest równy macierzy [n] lub, gdy wartości x nie ma w macierzy), a średnio n/2, jeśli x jest losowo umieszczony w jednej z n komórek macierzy. Przy macierzy 10 razy większej algorytm będzie 10 razy wolniejszy. Złożoność jest więc proporcjonalna do n, czyli „liniowa” względem n. Złożoność liniowa względem n jest uważana za szybką, w przeciwieństwie do złożoności wykładniczej względem n. Wprawdzie przetwarzanie więcej wartości na wejściu będzie wolniejsze, ale w większości zastosowań praktycznych będą to różnice sekundowe.
Jednak wiele użytecznych algorytmów jest wolniejsza i ma złożoność większą niż liniowa. Książkowym przykładem są algorytmy sortowania: mając listę n wartości w porządku losowym potrzebujemy średnio n × log n podstawowych działań, aby uporządkować listę, co czasami określa się jako złożoność liniowo-logarytmiczną (linearithmic complexity). Ponieważ n × log n rośnie szybciej niż n, szybkość sortowania będzie zwalniać szybciej niż proporcjonalnie do n, jednak takie algorytmy sortowania pozostają w przestrzeni obliczeń praktycznych, czyli obliczeń wykonywanych w rozsądnym czasie.
W jakimś punkcie dochodzimy do kresu możliwości stwierdzenia, czy coś daje się rozwiązać nawet dla relatywnie małych wielkości danych. Weźmy najprostszy przykład z kryptoanalizy: poszukiwanie siłowe tajnego klucza. Jak pamiętamy z rozdziału 1, mając tekst jawny P i szyfrogram C = E(K, P), znalezienie n-bitowego klucza symetrycznego wymaga 2n prób, gdyż mamy 2n możliwych kluczy – to przykład, gdy złożoność rośnie wykładniczo. Według teoretyków złożoność wykładnicza (exponential complexity) oznacza problem, który jest praktycznie niemożliwy do rozwiązania, gdyż w miarę jak n rośnie, nakład pracy potrzebny do znalezienia rozwiązania bardzo szybko staje się nieosiągalny.
Możemy mieć obiekcje, że porównujemy tu jabłka i pomarańcze: w funkcji search() z listingu 9.1 liczyliśmy liczbę operacji if (array[i] == x), podczas gdy znajdowanie klucza liczy liczbę szyfrowań, a każda jest tysiące razy wolniejsza od zwykłego porównania. Ta niespójność może mieć znaczenie, jeśli porównujemy dwa algorytmy o podobnej złożoności, ale w większości przypadków nie ma to znaczenia, gdyż liczba operacji będzie miała większy wpływ niż koszt pojedynczej operacji. Ponadto oszacowania złożoności ignorują składniki stałe: gdy mówimy, że algorytm zajmuje czas rzędu n 3 (co jest złożonością sześcienną), może to zająć w rzeczywistości 41 × n3 operacji lub 12345 × n3 operacji – ale i tak w miarę jak n rośnie, składniki stałe tracą na znaczeniu, aż do momentu, gdy można je zignorować. Analiza złożoności dotyczy teoretycznej trudności jako funkcji wielkości wejścia; nie ma znaczenia dokładna liczba cykli procesora potrzebnych na naszym komputerze.
KRYPTOGRAFIA KWANTOWA
I POSTKWANTOWA

W poprzednich rozdziałach tematem była dzisiejsza kryptografia, natomiast w tym rozdziale przeanalizujemy przyszłość kryptografii w horyzoncie czasowym, powiedzmy
100 lub więcej lat – w czasie, gdy będą istniały komputery kwantowe. Komputery kwantowe to komputery wykorzystujące fizykę kwantową w celu wykonywania innego rodzaju algorytmów niż te, do których jesteśmy przyzwyczajeni. Komputery kwantowe jeszcze nie istnieją i wygląda na to, że są trudne do zbudowania, ale jeśli kiedyś zaistnieją, będą miały potencjał do złamania RSA, Diffiego–Hellmana i kryptografii krzywych eliptycznych – czyli całej wdrożonej kryptografii klucza publicznego i standardów znanych w chwili pisania tej książki.
Aby zabezpieczyć się przed ryzykiem stwarzanym przez komputery kwantowe, badacze kryptografii opracowali alternatywne algorytmy kryptograficzne klucza publicznego, zwane algorytmami postkwantowymi, które będą
opierać się komputerom kwantowym. W roku 2015 NSA wezwała do przejścia na algorytmy odporne na komputery kwantowe opracowane tak, aby były bezpieczne, nawet gdy te się pojawią. W roku 2017 amerykańska agencja standaryzacji NIST zaczęła proces, który w rezultacie powinien doprowadzić do standaryzacji algorytmów postkwantowych. W tym rozdziale można więc znaleźć nietechniczny przegląd zasad, które spełniają komputery kwantowe, a także trochę informacji o algorytmach postkwantowych. Jest tu trochę matematyki, ale nic ponad podstawową arytmetykę i algebrę liniową, więc nie należy się obawiać trudnych notacji.
Jak działają komputery kwantowe
Komputery kwantowe to model obliczeniowy, który wykorzystuje fizykę kwantową, aby inaczej prowadzić obliczenia i wykonywać działania, jakich nie umieją robić klasyczne komputery, na przykład skuteczne złamanie RSA i kryptografii krzywych eliptycznych. Ale komputer kwantowy to nie jest superszybki zwykły komputer. W istocie komputery kwantowe nie potrafią rozwiązać żadnego problemu, który jest za trudny dla klasycznego komputera, jak poszukiwanie brutalne lub problemy NP-zupełne.
Komputery kwantowe są oparte na mechanice kwantowej, gałęzi fizyki badającej zachowanie cząstek subatomowych, które zachowują się naprawdę losowo. W przeciwieństwie do zwykłych komputerów, które działają na bitach i mają wartość 0 lub 1, komputery kwantowe są oparte na bitach kwantowych (quantum bits lub inaczej kubity), które jednocześnie mogą mieć wartość 0 i 1 – stan dwuznaczności zwany superpozycją. Fizycy odkryli, że w tym mikroskopowym świecie cząstki, takie jak elektrony i fotony, zachowują się w sposób wysoce nieintuicyjny: zanim zaobserwujemy elektron, nie znajduje się już on w określonym miejscu przestrzeni, lecz w kilku miejscach w tym samym czasie (czyli jest w stanie superpozycji). Ale gdy dokonamy obserwacji – działania nazywanego w fizyce kwantowej pomiarem – zatrzymuje się on w ustalonym losowym miejscu i przestaje znajdować się w superpozycji. Ta magia kwantowa pozwala na tworzenie kubitów w komputerze kwantowym. Ale komputery kwantowe działają tylko dlatego, że istnieje jeszcze bardziej zwariowane zjawisko zwane splątaniem – dwie cząstki mogą być połączone (splątane) w taki sposób, że obserwacja wartości jednej z nich daje nam wartość drugiej, jeśli nawet dwie cząstki są daleko od siebie (kilometry lub nawet lata świetlne od siebie). To zachowanie zostało zilustrowane przez paradoks EPR (Einstein–Podolsky–Rosen) i jest powodem, dla którego Albert Einstein na początku odrzucił mechanikę kwantową (szczegółowe wyjaśnienie można znaleźć na stronie https://plato.stanford.edu/entries/qt-epr/).
Aby najlepiej wytłumaczyć działanie komputera kwantowego, musimy odróżnić rzeczywisty komputer kwantowy (sprzęt złożony z bitów kwantowych) od algorytmów kwantowych (oprogramowania, które na nim działa, złożone z bramek kwantowych). Dwa poniższe punkty omawiają te dwa pojęcia.