Tryby działania szyfrów blokowych

sages.pl 7 lat temu
*Symetryczne szyfry blokowe* szyfrują wiadomości o określonej długości, która jest charakterystyczna dla wybranego algorytmu (np. 16 bajtów dla algorytmu AES, 8 bajtów dla algorytmu 3DES). Nazywamy je *blokami*. jeżeli blok jest zbyt krótki do przetworzenia przez algorytm kryptograficzny należy użyć *dopełnienia* (ang. *padding*). jeżeli wiadomość jest zbyt długa rozwiązaniem będzie podzielenie jej na bloki o odpowiedniej długości. *Tryb działania* (ang. *mode of operation*) szyfru blokowego to różne techniki łączenia bloków podczas operacji szyfrowania. Nazywane są również *trybem pracy szyfru blokowego* lub potocznie *trybem szyfrowania*. W poniższym wpisie przedstawię dwa z nich: tryb *elektronicznej książki kodowej* (ang. *electronic code book*, *ECB*) oraz tryb *wiązania bloków szyfrogramu* (ang. *cipher block chaining*, *CBC*). Oba tryby opisane zostały w standardzie [FIPS 81](http://csrc.nist.gov/publications/fips/fips81/fips81.htm).

## Elektroniczna książka kodowa
Szyfrowanie w trybie *elektronicznej książki kodowej* polega na niezależnym szyfrowaniu każdego bloku. Stąd wywodzi się również nazwa tego trybu. Książka kodowa to zbiór pewnych fraz i odpowiadających im fraz po zakodowaniu (w tym przypadku zaszyfrowaniu). jeżeli dla danego klucza (a bloki z wiadomości szyfrujemy tym samym kluczem) utworzymy wszystkie możliwe teksty jawne i kryptogramy im odpowiadające otrzymamy książkę kodową. Książek kodowych będzie tyle ile możliwych kluczy dla danego szyfru. Aby szyfrować dzięki danej książki kodowej należy znaleźć odpowiedni tekst jawny i odpowiadający jej kryptogram. Podobnie będzie przebiegała operacja deszyfrowania.

![ecb.webp](/uploads/ecb_246aeac481.webp)

Poniższy przykład szyfruje i deszyfruje dwa bloki danych w trybie ECB. Zachęcam do uzupełnienia go o fragment kodu wypisujący na ekranie tekst jawny i kryptogram oraz do przyjrzenia się wynikom. Można skorzystać z metody `printHexBinary(byte[] val)` klasy `javax.xml.bind.DatatypeConverter`.

```java
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class ECBTest {
public static void main(String[] args) throws Exception {
byte[] plain = "abcdefghijklmnopabcdefghijklmnop".getBytes();
byte[] key = "klucz--128-bitow".getBytes();

SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");

// szyfrowanie
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte encrypted[] = cipher.doFinal(plain);

// deszyfrowanie
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte decrypted[] = cipher.doFinal(encrypted);
}
}
```

Jeśli dwa razy tym samym kluczem zaszyfrujemy identyczne bloki to otrzymamy dwa identyczne szyfrogramy. Jest to zgodne z ideą książki kodowej. Oczywiście atakujący nie wie jaki był tekst jawny, ale już wie, iż dwa razy zaszyfrowano taki sam blok. jeżeli jego wiedza o budowie tekstu jawnego będzie większa, może on w prosty sposób wykorzystać przekazywane szyfrogramy do ataku. Nie będzie jednak próbował ich zdeszyfrować a jedynie będzie manipulował ich kolejnością. Przyjmijmy, iż kwota przelewu przekazywana jest w postaci zaszyfrowanej algorytmem AES jako 32 cyfry (2 bloki po 16 bajtów). Jest dużo bardziej prawdopodobne, iż znaczące cyfry znajdują się w drugim bloku niż w pierwszym, ponieważ z reguły przelewy nie są wykonywane na tak duże kwoty. Atakujący nie jest w stanie zdeszyfrować kryptogramów, ale może zamienić je miejscami. Uzyska jeszcze więcej jeżeli usunie pierwszy kryptogram i zastąpi go drugim. System odbierający dane rozszyfruje je prawidłowo (będą to cyfry), jednak kwota prawdopodobnie będzie znacząco wyższa. Atakujący manipulując kryptogramem dokonał udanego ataku. Bardzo istotne jest również to, iż **wykorzystując bezpieczny algorytm** (AES) w **niebezpieczny sposób** (zły tryb działania) **otrzymaliśmy niebezpieczny system**! Warto samodzielnie spróbować dokonać takiego ataku modyfikując przykładowy program.

Innym dobrym przykładem jest [obrazek tego pingwina](https://upload.wikimedia.org/wikipedia/commons/5/56/Tux.jpg), o którego poufności ciężko mówić po [zaszyfrowaniu w trybie ECB](https://upload.wikimedia.org/wikipedia/commons/f/f0/Tux_ecb.webp). Dany kolor w jawnym obrazku po zaszyfrowaniu stał się innym kolorem, ale zawsze takim samym.

Czy powyższe przykłady wskazują jednoznacznie, iż tryb ten jest *niebezpieczny* i nie powinno się go w ogóle używać? Moim zdaniem nie zawsze. Wszystko zależy od tego w jakich zastosowaniach jest używany i czy jesteśmy świadomi do jakich ataków może to doprowadzić. Dużo poważniejszym błędem niż wykorzystanie trybu ECB w pierwszym przykładzie ataku był brak zapewnienia uwierzytelnienia źródła danych.

Na problem bezpieczeństwa trybu ECB można spojrzeć jeszcze w inny sposób. Wykorzystując rozumowanie z poprzedniego artykułu związane z czarną skrzynką możemy wyobrazić sobie w niej szyfrator ECB. Czy atakujący może w prosty sposób odróżnić go od generatora liczb losowych przesyłając wiadomości i obserwując odpowiedzi? Może. Czytelnikom pozostawiam znalezienie przykładowych zapytań do takiej czarnej skrzynki umożliwiających efektywne rozróżnienie jej od generatora liczb losowych.

## Wiązanie bloków szyfrogramu
Tryb działania z *wiązaniem bloków szyfrogramu* (*wiązaniem bloków zaszyfrowanych*) eliminuje podstawowe wady trybu *elektronicznej książki kodowej* tworząc zależność pomiędzy poszczególnymi blokami dzięki operacji *xor*. Przed zaszyfrowaniem blok poddawany jest działaniu *xor* z poprzednim kryptogramem. Taka sama operacja wykonywana jest po rozszyfrowaniu co umożliwia odzyskanie tekstu jawnego, ponieważ dwukrotny *xor* z tymi samymi danymi usuwa przekształcenie przed zaszyfrowaniem przywracając oryginalny tekst jawny. Pierwszy blok nie ma poprzedzającego kryptogramu dlatego operacja *xor* wykonywana jest z tak zwanym *wektorem początkowym* (ang *initialization vector*, *IV*), zwanym również *wektorem początkowym*.

![cbc.webp](/uploads/cbc_1396dd4747.webp)

Program wykorzystywany wcześniej do szyfrowania w trybie ECB będzie wymagał kilku modyfikacji. Poza zmianą trybu niezbędne będzie wprowadzenie parametru reprezentującego wektor początkowy. Zawiera go instancja klasy `IvParameterSpec`. Ponownie zachęcam do uzupełnienia kodu o fragment wypisujący na ekranie tekst jawny i kryptogram oraz do przyjrzenia się wynikom.

```java
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;

public class CBCTest {
public static void main(String[] args) throws Exception {
byte[] plain = "abcdefghijklmnopabcdefghijklmnop".getBytes();
byte[] key = "klucz--128-bitow".getBytes();
byte[] iv = "-blok-wektor-iv-".getBytes();

SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
IvParameterSpec ivps = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");

// szyfrowanie
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivps);
byte encrypted[] = cipher.doFinal(plain);

// deszyfrowanie
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivps);
byte decrypted[] = cipher.doFinal(encrypted);
}
}
```

Jaki powinien być wektor początkowy? Rozważając eksperyment z czarną skrzynką: jeżeli wektor początkowy będzie przewidywalny lub wręcz za każdym razem taki sam (a to często zdarza się w działających systemach) można się go łatwo pozbyć odpowiednio preparując pierwszy blok. Przed zaszyfrowaniem wykonujemy *xor* z przewidywanym wektorem początkowym. Tryb wykona powtórny *xor* usuwając zmiany wprowadzone przez IV. W ten sposób zaszyfrowany zostanie pierwszy blok w swojej oryginalnej wartości, bez wpływu wektora inicjującego. Dlatego też wektor początkowy powinien być losowy (co w kryptografii implikuje założenie o jego unikalności). Nie ma przeciwwskazań aby wektor początkowy był przekazywany w formie jawnej. Można go traktować jako nieużywaną część kryptogramu (wektory inicjujące kolejnych bloków są poprzedzającymi kryptogramami, które przekazujemy w niezabezpieczonym kanale).

Zatem modyfikując powyższy przykład zamiast inicjować wektor IV tymi samymi danymi należałoby użyć tam danych losowych:

```java
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
```

Po takiej zmianie kryptogram każdorazowo będzie wyglądał inaczej. Pingwin, o którym była mowa wcześniej, zaszyfrowany w trybie CBC może wyglądać [jak na tym rysunku](https://upload.wikimedia.org/wikipedia/commons/a/a0/Tux_secure.webp). Jest już w pełni ukryty.

Czy poza tymi zaletami tryb CBC ma jakieś wady? Niestety tak. Nie ma wprawdzie możliwości dowolnego manipulowania tekstem jawnym poprzez modyfikacje w kryptogramie, ale łatwo usunąć jego początek (usuwając początek kryptogramu i podmieniając wektor początkowy) lub jego koniec (poprzez usunięcie końca kryptogramu). Można tego uniknąć przesyłając w kryptogramie długość całej oryginalnej wiadomości. Każdorazowo należy pamiętać o wspomnianej już losowości wektora inicjującego.

## Podsumowanie
Tryby działania szyfrów blokowych wykorzystywane są, gdy szyfrujemy więcej niż pojedynczy blok danych. Odpowiednie dobranie trybu może mieć najważniejsze znaczenie dla bezpieczeństwa, o czym przekonaliśmy się na kilku prostych przykładach. Należy zwrócić uwagę na to, iż oba przedstawione tryby działania nie zapewniają integralności oraz uwierzytelnienia przesyłanych danych. Oczywiście można by uzupełnić je o niezależnie obliczany *MAC*. Istnieją tryby, które zostały zaprojektowane z myślą o tych niezbędnych usługach. Są to tak zwane tryby pracy szyfrów blokowych do realizacji *uwierzytelnionego szyfrowania* (ang. *authenticated encryption*, *AE*) oraz *uwierzytelnionego szyfrowania z dodatkowymi danymi* (ang. *authenticated ecnryption with additional data*, *AEAD*), w których zapewnione jest również uwierzytelnienie przesyłanych danych zaszyfrowanych i jawnych. Trybów pracy szyfrów blokowych jest całe mnóstwo, wiele z nich wraz z analizami bezpieczeństwa i aktualnymi zaleceniami wymienionych jest na [stronach NIST](http://csrc.nist.gov/groups/ST/toolkit/BCM/index.html).
Idź do oryginalnego materiału