Szyfr strumieniowy Rabbit
Transkrypt
Szyfr strumieniowy Rabbit
Układy cyfrowe – projekt. Temat: Implementacja szyfru strumieniowego Rabbit w układach FPGA. Specyfikacja końcowa – uwzględnia poprawki i uzupełnienia dodane na etapie implementacji. prowadzący: mgr inż. Tomasz Wojciechowski zespół projektowy: Wojciech Kożuch, g. 4T1 Paweł Król, g. 4T4 Krzysztof Siejkowski, g. 4T1 Opis algorytmu: Algorytm ukryty pod nazwą „Rabbit” to synchroniczny szyfr strumieniowy zaprezentowany po raz pierwszy w 2003r. Jak dotąd nie wykryto w nim żadnych słabych punktów (z kryptograficznego punktu widzenia). Używając klucza o długości 128 bitów oraz (opcjonalnie) IV (wektora inicjalizacyjnego) o długości 64 bitów generuje on w kolejnych iteracjach wyjściowe bloki 128 pseudolosowych bitów. Są one kombinacją 513 bitów określających stan wewnętrzny. Szyfrowanie/deszyfrowanie polega na XOR-owaniu wygenerowanych pseudolosowych bloków odpowiednio z tekstem szyfrowanym dla zaszyfrowania lub z tekstem zaszyfrowanym dla uzyskania oryginalnej wiadomości. Stan wewnętrzny to 513 bitów podzielonych pomiędzy osiem 32 bitowych zmiennych stanu (state variables), osiem 32 bitowych liczników (counters) i jeden bit przeniesienia liczników (counter carry bit). Wszystkie zmienne stanu są aktualizowane przez osiem sprzężonych funkcji nieliniowych Szyfr “Rabbit” zapewnia możliwość zaszyfrowania do 264 bloków tekstu (po 128 bitów każdy). Oznacza to, że odczytanie zaszyfrowanej wiadomości o co najwyżej 264 blokach tekstu wymaga co najmniej tyle kroków co poszukiwanie jednego (właściwego) z 2128 kluczy. Pierwszy etap działania szyfru Rabbit to pobranie 128-bitowego klucza i podzielenie go na 16 32-bitowych rejestrów - 8 zmiennych stanu (x) i 8 liczników (c). Następnie wykonywane są cztery iteracje, w których następuje modyfikacja liczników (zwiększenie o stałą - dodawanie, modyfikacja bitu podtrzymania liczników - dodawanie i dzielenie mod, dzielenie mod), wyliczenie g (potęgowanie, xorowanie, dzielenie mod) i wyliczenie x (dodawanie i przesunięcie bitowe). W wyniku iteracji likwiduje się prostą zależność stanu wewnętrznego od klucza. Na kolejnym odcinku, w celu zapewnienia niemożliwości odwrócenia procesu, następuje reinicjalizacja liczników - zmienne licznikowe xorowane są ze zmiennymi stanu. Potem liczniki modyfikowane (xorowanie) są przez IV (wektor inicjalizujący). Znów wykonywane są 4 iteracje, tak jak po pobraniu klucza. Czterokrotna iteracja zapewnia brak liniowej zależności między kluczem a wektorem. Teraz szyfr jest już prawidłowo zainicjowany. Każda kolejna iteracja zmodyfikuje zmienne x, tak, że będą mogły być traktowane jako pseudolosowe. W wyniku xorowania rejestrów x po kolejnych iteracjach otrzymujemy kolejne 128-bitowe bloki strumienia szyfrującego s, który po kolejnym xorowaniu z tekstem daje zaszyfrowaną wiadomość. Dokładny opis algorytmu: http://www.ecrypt.eu.org/stream/p3ciphers/rabbit/rabbit_p3.pdf Interfejs układu: Nazwa sygnału Typ portu Szerok ość szyny 128 bitów INPUT Wejście CLOCK RESET START Wejście Wejście Wejście 1 bit 1 bit 1 bit SLEEP Wejście 1 bit INIT Wejście 1 bit OUTPUT Wyjście 128 bitów DONE Wyjście 1 bit KEY_OK Wyjście 1 bit IV_OK Wyjście 1 bit SLEEP_OK Wyjście 1 bit Uwagi dodatkowe Wejście dla danych: klucza, wektora inicjalizującego, tekstu do zaszyfrowania/zdeszyfrowania. Wchodzi bezpośrednio na wejście rejestru. Zegar sterujący układem. Reset asynchroniczny. Wartość 1 sygnalizuje, że układ ma rozpocząć działanie i pobrać tekst z wejścia INPUT. Przy wartości 0 układ jest uśpiony aż do pojawienia się wartości 1. Sygnał usypiający układ (wstrzymujący jego pracę). Wartość '0' – układ ma pracować. Wartość '1' – układ śpi. Informuje układ o tym, że użyty ma być podany na wejście IV wektor inicjalizacyjny. Jeśli na wejściu wartość 1, układ pobierze IV i przejdzie do inicjalizacji, jeśli 0, układ ominie ten krok. Zaszyfrowany/odszyfrowany tekst (w zależności od tego, co podano na wejściu). Wyjście z rejestru, w blokach po 128 bitów. Informuje o tym, czy można pobrać z wyjścia kolejny blok tekstu i podać kolejny na wejście. Wartość 1 – można. Dokładny opis poniżej. Informuje o tym, że trzeba podać klucz na wejście INPUT. Wartość 0 – klucz już zbędny, wartość 1 – klucz potrzebny. Informuje, że można podać wektor inicjalizacyjny na wejście INPUT. Wartość 0 – IV już zbędny, wartość 1 – IV potrzebny. Informuje, że układ jest w stanie uśpienia. Wartość '1' – układ śpi. Wartość '0' – układ pracuje. Wszystkie wyjścia układu prócz SLEEP_OK są buforowane, to znaczy podpięte bezpośrednio do wyjść odpowiednich rejestrów, co pozwala uniknąć szpilek na wyjściu. Sposób startowania układu: Aby układ wystartował, należy podać wartość '1' na wejście START. W momencie pojawienia się wartości '0' na KEY_OK (klucz pobrany, układ rozpoczął pracę) można tę wartość zdjąć. Sposób usypiania układu: Aby układ przeszedł w stan wstrzymania (uśpienia) należy podać wartość '1' na wejście SLEEP. Gdy układ przejdzie w uśpienie, na wyjściu SLEEP_OK pojawi się wartość '1'. Układ będzie wstrzymany tak długo, aż na wejściu SLEEP będzie podane '1'. Gdy na wejściu SLEEP zostanie podane '0', układ zacznie pracę, co zostanie zasygnalizowane wartością '0' na wyjściu SLEEP_OK. Sposób podawania klucza na wejście INPUT: W momencie pojawienia się wartości '1' na KEY_OK (narastające zbocze) należy podać klucz, inaczej układ szyfrować będzie kluczem zerowym. W momencie pojawienia się wartości '0' na KEY_OK (opadające zbocze) klucz można już zdjąć. Sposób nakazania układowi uwzględnienia wektora inicjalizującego: Aby układ pracował z uwzględnieniem wektora inicjalizującego, należy na wejście INIT podać wartość '1'. Wówczas układ pobierze IV i rozpocznie inicjalizację. W tryb pracy z IV przejść można w dowolnym momencie pracy układu, jeśli wraz z pojawieniem się narastającego zbocza na wyjściu IV_OK zapewni się wartość '1' na INIT i utrzyma aż do pobrania IV z wejścia INPUT (patrz niżej).. Wektor inicjalizacyjny może być podawany wielokrotnie; za każdym razem układ powróci wówczas do stanu wewnętrznego sprzed modyfikacji wektorem IV, następnie dokona tej modyfikacji nową wartością wektora, i przejdzie do normalnej pracy. Sposób podawania wektora inicjalizującego na wejścia INPUT: W momencie pojawienia się '1' na IV_OK (narastające zbocze) można podać IV na 64 młodszych bitach. Wektor zostanie pobrany i inicjalizacja przeprowadzona, jeśli na wejściu INIT będzie podana wartość '1'. Jeśli na wejściu INIT będzie wartość '0', układ nie uwzględni wektora inicjalizującego. Gdy na IV_OK pojawi się wartość '0' (opadające zbocze), można zdjąć już wektor IV z INPUT. Sposób podawania tekstu na wejście INPUT: Kolejny blok podać można, gdy na wyjściu DONE pojawi się wartość '1' (czyli wraz z narastającym zboczem sygnału na wyjściu DONE). Blok ten należy utrzymywać na wejściu aż do pojawienia się na wyjściu DONE wartości '0' (opadające zbocze sygnału na wyjściu DONE) bądź też aż do kolejnego narastającego zbocza sygnału. Sugerowana, najprostsza obsługa układu zakłada podawanie bloków tekstu w takt narastającego zbocza sygnału DONE – wówczas gwarantowana jest prawidłowa praca układu. Sposób pobierania tekstu z wyjścia OUTPUT: Kolejny blok przetworzonego tekstu pobrać można, gdy na wyjściu DONE pojawi się wartość '1'. Sugerowana, najprostsza obsługa układu zakłada pobieranie bloków tekstu z wyjścia OUTPUT w takt narastającego zbocza sygnału DONE – wówczas gwarantowana jest prawidłowa praca układu. Schemat układu: Schemat układu podzielono na schemat ogólny oraz szereg schematów szczegółowych, wyjaśniających budowę bloków spełniających dane funkcje wraz z dokładnym wypisaniem, które sygnały wchodzą do którego bloku. Dla uproszczenia przyjęto jednolitą konwencję graficzną nazw sygnałów: kursywą zapisano sygnały sterujące, wytłuszczono sygnały interfejsu, a sygnały wewnętrzne zapisano bez wyróżnień. Nie przedstawiono bloku automatu sterującego, ale należy rozumieć, że ma on wyjścia odpowiadające wejściom sterującym rejestrów oraz multiplekserów i demultiplekserów. Jeśli sygnały interfejsu nie zostały wyróżnione na schemacie, oznacza to, że za ich obsługę odpowiedzialny jest automat sterujący. Oddzielono kolorami bloki funkcyjne oraz wyraźnie przedstawiono część kombinacyjną. Pominięto dla przejrzystości sygnały resetu oraz zegara przy rejestrach, lecz one oczywiście są tam doprowadzane. W schematach pominięto trzy 1-bitowe rejestry buforujące wyjścia DONE, KEY_OK i IV_OK układu. Są to rejestry przyjmujące wartość bezpośrednio z automatu sterującego (zgodnie z sygnałem sterującym) i podające ją na wyjście. Sygnałami sterującymi są D_R (dla rejestru na wyjściu DONE), K_R (dla rejestru na wyjściu KEY_OK) oraz V_R (dla rejestru na wyjściu IV_OK). Na schemacie pominięto również dla czytelności jeden 1-bitowy multiplekser na wejściu rejestru przechowującego wartość FI. Jest on sterowany sygnałem FI_MUX, kieruje do rejestru FI wartość albo z bloku inkrementacji (po kolejnej iteracji), albo z rejestrów kopii zapasowych (przy bloku modyfikacji wektorem IV). Schemat ogólny układu: x0,i+1 ... x7,i+1 (po kolejnej iteracji) [256 bitów] c0,i+1 ... c7,i+1 (po kolejnej iteracji) [256 bitów] KEY [128 bitów] KEY [128 bitów] c0' ... c7' (po reinicjalizacji) [256 bitów] FI_REG Rejestr 1bitowyφ7 [1 bit] c0' ... c7' (po modyfikacji IV) [256 bitów] stałe X_MUX C_MUX [1 bit] [2 bity] [256 bitów] [256 bitów] 8 rejestrów 32-bitowych X_R przechowujących [1 bit] x0 ... x7 8 rejestrów 32-bitowych przechowujących c0 ... c7 [256 bitów] [256 bitów] X_DMUX [2 bity] Modyfikacja c0 ... c7 wektorem IV C_R IV [64 bity] a0 ... a7 [256 b] Inkrementacja wartości c0 ... c7 c0 ... c7 [256 bitów] φ7 [1 bit] [1 bit] B_R 16 rejestrów 32-bitowych [1 bit] przechowujących c0 ... c7 i x0 ... x7 C_DMUX [2 bity] c0 ... c7 [256 bitów] Reinicjalizacja wartości c0 ... c7 G_R [2 bity] Wyliczenie wartości x0 ... x7 x0 ... x7 [256 bitów] c0 ... c7 [256 bitów] c0,i ... c7,i (przed kolejną iteracją) [256 bitów] c0 ... c7 [256 bitów] x0 ... x7 [256 bitów] x0,i ... x7,i (przed kolejną iteracją) [256 bitów] INPUT [128 bitów] x0 ... x7 I_R [256 bitów] [1 bit] Rejestr 128- I_DMUX [2 bity] bitowy wejściowy IV [64 b L] KEY [128 b] Generowanie strumienia szyfrującego oraz kodowanie/dekodowanie tekstu O_R [1 bit] [128 bitów] Rejestr 128bitowy wyjściowy OUTPUT [128 bitów] Schemat bloku modyfikacji wartości c0 ... c7 wektorem IV: IV [47..32, IV [47..32, IV [63..48, IV [63..48, c3 [32 bity] 15..0] c1 [32 bity] 31..16] c5 [32 bity] 31..16] c7 [32 bity] 15..0] c0 [32 bity] IV [31..0] c2 [32 bity] IV [63..32] c4 [32 bity] IV [31..0] c [32 bity] IV [63..32] XOR 32bitowy XOR 32bitowy XOR 32bitowy XOR 32bitowy c3' [32 bity] c1' [32 bity] c0' [32 bity] 6 XOR 32bitowy XOR 32bitowy c2' [32 bity] XOR 32bitowy XOR 32bitowy c5' [32 bity] c4' [32 bity] c7' [32 bity] c6' [32 bity] Schemat bloku reinicjalizacji wartości c0 ... c7 : c0 [32 bity] x4 [32 bity] c1 [32 bity] x5 [32 bity] c2 [32 bity] x6 [32 bity] XOR 32c3 [32 bity] x7 [32 bity] bitowy c4 [32 bity] x0 [32 bity] XOR 32bitowy XOR 32c5 [32 bity] x1 [32 bity] bitowy XOR 32c6 [32 bity] x2 [32 bity] c0' [32 bity] bitowy XOR 32bitowy c1' [32 bity] XOR 32c7 [32 bity] x3 [32 bity] c2' [32 bity] bitowy XOR 32c3' [32 bity] bitowy c4' [32 bity] XOR 32c5' [32 bity] bitowy c6' [32 bity] c7' [32 bity] Schemat bloku generowania strumienia oraz szyfrowania/deszyfracji tekstu: x0 [16L b] x5 [16H b] x0 [16H b] x3 [16L b] x2 [16L b] x7 [16H b] XOR 16bitowy INPUT [15..0] x2 [16H b] x5 [16L b] XOR 16bitowy INPUT [31..16] XOR 16bitowy XOR 16bitowy INPUT [47..32] XOR 16bitowy XOR 16bitowy Schemat bloku inkrementacji wartości c0 ... c7 : φ7 [1 b] a0 [32 b] c0 [32 b] Sumator 32bitowy z przeniesieniem φ0 [1 b] a1 [32 b] c1 [32 b] Sumator 32bitowy z przeniesieniem c0' [32 bity] c1' [32 bity] x4 [16L b] x1 [16H b] a2 [32 b] c2 [32 b] φ1 [1 b] x4 [16H b] x7 [16L b] XOR 16bitowy INPUT [63..48] INPUT [79..64] XOR 16bitowy Rejestr potokowy 1-bitowy x6 [16L b] x3 [16H b] XOR 16bitowy XOR 16bitowy FI_S1_R [1 b] XOR 16bitowy INPUT [95..80] XOR 16bitowy XOR 16bitowy x6 [16H b] x1 [16L b] XOR 16bitowy INPUT [111..96] INPUT [127..112] XOR 16bitowy XOR 16bitowy a3 [32 b] c3 [32 b] Sumator 32- φ [1 b] Rejestr a4 [32 b] c4 [32 b] FI_S2_R 2 bitowy z potokowy φ3 [1 b] [1 b] a5 [32 b] c5 [32 b] Sumator 32przeniesieniem 1-bitowy bitowy z φ4 [1 b] przeniesieniem Sumator 32a6 [32 b] c6 [32 b] bitowy z a7 [32 b] c7 [32 b] c2' [32 bity] Sumator 32- φ [1 b] przeniesieniem φ6 [1 b] 5 bitowy z c3' [32 bity] Sumator 32przeniesieniem Sumator 32- φ7' [1 b] bitowy z c4' [32 bity] bitowy z przeniesieniem przeniesieniem c5' [32 bity] c6' [32 bity] c7' [32 bity] Schemat bloku wyliczania wartości x0 ... x7 : c [32 b] x1 [32 b] 1 c0 [32 b] x0 [32 b] Sumator 32bitowy [32L] [32H] XOR 32bitowy G_R [1 b] Rejestr 32bitowy c7 [32 b] x7 [32 b] Sumator 32bitowy Sumator 32bitowy Sumator 32bitowy c2 [32 b] x2 [32 b] Układ 32bitowy podnoszący do kwadratu Układ 32bitowy podnoszący do kwadratu Sumator 32bitowy c4 [32 b] x4 [32 b] Sumator 32bitowy Układ 32bitowy podnoszący do kwadratu XOR 32bitowy XOR 32bitowy [32L] [32H] G_R [1 b] G_R [1 b] XOR 32bitowy Rejestr 32bitowy Rejestr 32bitowy G_R [1 b] g1 [32 b] Rejestr 32bitowy g116 [32 b] g3 [32 b] g2 [32 b] g2 16 8 [32 b] g2 [32 b] g316 [32 b] c6 [32 b] x6 [32 b] Układ 32bitowy podnoszący do kwadratu Sumator 32bitowy Układ 32bitowy podnoszący do kwadratu [32L] [32H] XOR 32bitowy G_R [1 b] Rejestr 32bitowy Układ 32bitowy podnoszący do kwadratu [32L] [32H] [32L] [32H] [32L] [32H] g0 [32 b] g08 [32 b] g016 [32 b] c5 [32 b] x5 [32 b] Sumator 32bitowy [32L] [32H] Układ 32bitowy podnoszący do kwadratu c3 [32 b] x3 [32 b] Układ 32bitowy podnoszący do kwadratu XOR 32bitowy [32L] [32H] G_R [1 b] XOR 32bitowy Rejestr 32bitowy G_R [1 b] g5 [32 b] g516 [32 b] Rejestr 32bitowy g4 [32 b] g6 [32 b] g416 [32 b] g48 [32 b] g616 [32 b] g68 [32 b] XOR 32bitowy G_R [1 b] Rejestr 32bitowy g7 [32 b] g716 [32 b] g0 [32 b] g2 [32 b] g616 [32 b] x0 [32 bity] g4 [32 b] g016 [32 b] 8 g0 [32 b] g1 [32 b] g7 [32 b] x2 [32 bity] g6 [32 b] g216 [32 b] 8 g2 [32 b] g3 [32 b] g1 [32 b] Sumator 32bitowy Sumator 32bitowy x1 [32 bity] x3 [32 bity] x4 [32 bity] g416 [32 b] Sumator 32bitowy Sumator 32bitowy Sumator 32bitowy Sumator 32bitowy g516 [32 b] g316 [32 b] g116 [32 b] g716 [32 b] g48 [32 b] g5 [32 b] g3 [32 b] x6 [32 bity] g68 [32 b] g7 [32 b] g5 [32 b] Sumator 32bitowy Sumator 32bitowy x5 [32 bity] x7 [32 bity] Opis działania: Sygnał na wejściu START uruchamia układ. W tym samym cyklu na wejście INPUT podawany jest klucz. Układ używa klucza do zainicjalizowania swego stanu wewnętrznego, następnie oblicza kolejne iteracje zgodnie z algorytmem. Po 4 iteracjach dokonywane jest dodatkowa reinicjalizacja liczników. Sprawdzane jest wejście INPUT – jeśli podano na nim wektor inicjalizacyjny (sygnał na wejściu INIT), dokonywana jest modyfikacja liczników, jeśli zaś wejście jest puste (wartość zerowa), układ przechodzi do szyfrowania. Na wejście INPUT podawany jest w blokach po 128 bitów tekst zaszyfrowany lub jawny. Jest on ładowany do pamięci wewnętrznej układu, który następnie XORując pobrany blok z iteracją algorytmu tworzy w nowym miejscu pamięci wewnętrznej blok zaszyfrowany/odszyfrowany. Po przetworzeniu całego bloku jest on podawany na wyjście, a układ pobiera kolejny blok informacji. Cykl powtarza się wielokrotnie. Opis budowy układu: Dane w układzie przechowywane są w rejestrach. Wartości x zajmują 8 32-bitowych rejestrów, podobnie wartości c. Wartość bitu przeniesienia liczników przechowywana jest w jednobitowym rejestrze. Ponadto użyte są dwa rejestry 128-bitowe (wejściowy i wyjściowy), rejestry kopii zapasowej przy modyfikacji IV (16 32-bitowych i 1 1-bitowy), dwa 1-bitowe rejestry potokowe przy inkrementacji liczników, jeden trzy 1-bitowe rejestry buforujące wyjścia układu, 8 rejestrów 32-bitowych do wartości g oraz jeden rejestr 48-bitowy do przechowywania stanu wewnętrznego automatu (w kodowaniu ONE-HOT, 48 stanów). Wszystkie rejestry (prócz niejawnego rejestru automatu) mają doprowadzone sterowanie sygnałami HOLD, LOAD, CLOCK i RESET. Układ wykorzystuje 1591 bitów rejestrów, 16 multiplekserów 32-bitowych, jeden multiplekser 1-bitowy, 20 demultiplekserów 32-bitowych, 24 XORy 32-bitowe, 16 XORów 16-bitowych, 8 sumatorów 32-bitowych z przeniesieniem, 16 sumatorów 32-bitowych bez przeniesienia i 8 układów podnoszących do kwadratu liczbę 32-bitową. Automat sterujący: Sterowanie odbywa się poprzez automat sterujący. Zapewnia on obsługę następujących sygnałów (wyjścia automatu): Sterowanie multiplekserów podający 256 bitowy sygnał X_MUX, C_MUX, wewnętrzny (zmienne x, liczniki c) z odpowiedniego wejścia FI_MUX (klucz, bloki funkcyjne) na 8 32-bitowych rejestrów. Sygnały dwubitowe i jednobitowe. I_DMUX Sterowanie demultiplekserami na wyjściu rejestru wejściowego. W zależności od potrzeby, sygnał pobrany z wejścia INPUT kierowany jest do szyn KEY, do szyn IV lub wprost do bloku szyfrowania tekstu. Sygnał dwubitowy. Sterowanie demultiplekserów kierunkujących 256 bitowy sygnał X_DMUX, C_DMUX wewnętrzny (zmienne x, liczniki c) z 8 32-bitowych rejestrów na odpowiednie wejście bloku funkcyjnego. Sygnały dwubitowe. X_R, C_R, G_R, I_R, O_R, B_R, FI_REG, Sterowanie rejestrów, jednobitowe. We wszystkich przypadkach FI_S1_R, FI_S2_R, sygnał 0 oznacza HOLD, sygnał 1 to LOAD. D_R, V_R, K_R KEY_OK, IV_OK, SLEEP_OK, DONE Sygnały należące do interfejsu, opis w opisie interfejsu. Jednobitowe. Na wyjściu automatu znajduje się 27 bitów sterowania. Warto zauważyć, że sygnały rejestrów oraz multiplekserów i demultiplekserów, mimo, iż dostarczane do wielu bloków funkcyjnych naraz, w istocie mogą być opisywane i traktowane wspólnie, gdyż sterowanie odbywa się grupami rejestrów, a nie pojedynczo. Wejścia automatu to START, SLEEP, INIT, CLOCK oraz RESET, wszystkie jednobitowe. Pod wpływem tych wejść stan automatu może się zmieniać. Sygnał RESET zeruje wszystkie rejestry oraz wprowadza automat w stan S0, niezależnie od aktualnego stanu. Stany automatu: Uwaga! Jeśli nie wypisano zmiany wartości sygnału, oznacza to, że jest na nim wartość jak w stanie poprzednim! lp. nazwa w kodzie Opis stanu i sygnałów sterujących w danym stanie. VHDL S0 preinit oczekiwanie na sygnał START; wszystkie wyjścia automatu są wyzerowane, prócz KEY_OK, na którym w drugim cyklu zegara pojawia się wartość '1'; D_R, V_R i K_R mają wartość LOAD; S1 take_key pobranie klucza; I_R ma wartość LOAD; I_DMUX kieruje na szynę KEY, K_R ładuje wartość '0' do rejestru wyjściowego; S2 initializing inicjalizacja szyfru; X_MUX i D_MUX kierują z szyny KEY do rejestrów, X_R i C_R mają wartość LOAD; I_R ma wartość HOLD; KEY_OK i IV_OK - wartość 0; S3 c_inc1 inkrementacja liczników – część pierwsza (1); X_R, C_R mają wartość HOLD, FI_S1_R ma wartość LOAD; S4 c_inc1_2 inkrementacja liczników – część druga (1); FI_S1_R ma wartość HOLD, FI_S2_R ma wartość LOAD; S5 c_inc1_3 inkrementacja liczników – część trzecia (1); C_DMUX kieruje na inkrementację, FI_REG ma wartość LOAD, C_MUX kieruje z inkrementacji; C_R ma wartość LOAD; FI_S2_R ma wartość HOLD; S6 g_calc1 wyliczenie wartości g (1); C_R i FI_REG mają wartość HOLD, X_DMUX i C_DMUX kierują na blok wyliczania x, G_R ma wartość LOAD; S7 x_calc1 wyliczanie wartości x (1); X_R to LOAD, X_MUX kieruje z bloku wyliczania x; FI_S1_R ma wartość LOAD; G_R ma wartość HOLD; S8 c_inc2 inkrementacja liczników – część pierwsza (2); FI_S1_R ma wartość HOLD; FI_S2_R ma wartość LOAD; S9 c_inc2_2 inkrementacja liczników – część druga (2); C_DMUX kieruje na inkrementację, FI_REG ma wartość LOAD, C_MUX kieruje z inkrementacji; C_R ma wartość LOAD; FI_S2_R ma wartość HOLD; S10 g_calc2 wyliczenie wartości g (2); C_R i FI_REG mają wartość HOLD, X_DMUX i C_DMUX kierują na blok wyliczania x, G_R ma wartość LOAD; S11 x_calc2 wyliczanie wartości x (2); X_R to LOAD, X_MUX kieruje z bloku wyliczania x; G_R to HOLD; FI_S1_R ma wartość LOAD; S12- c_inc3, c_inc3_2, S19 g_calc3, x_calc3, c_inc4, c_inc4_2, g_calc4, x_calc4, Powielenie stanów S8-S11 (tak, że automat wykonuje jeszcze dwie iteracje składające się z inkrementacji liczników, wyliczenia g oraz wyliczenia x; w ostatnim stanie V_R ma wartość LOAD; S20 c_reinit reinicjalizacja liczników; V_R ma wartość HOLD, IV_OK to '1'; X_R to HOLD, C_DMUX kieruje na blok reinicjalizacji, C_MUX z inicjalizacji, C_R ma wartość LOAD; V_R to HOLD; S21 backup zapisanie stanu wewnętrznego; X_DMUX i C_DMUX kierują na blok IV, B_R ma wartość LOAD; C_R ma wartość HOLD; S22 take_iv pobranie wektora IV; I_R ma wartość LOAD, V_R to LOAD; S23 iv_mod modyfikacja wektorem IV; C_MUX kieruje z bloku IV, B_R ma wartość HOLD, C_R ma wartość LOAD, FI_REG to LOAD, X_R to LOAD; IV_OK ma wartość 0; V_R to HOLD; I_DMUX kieruje na szynę IV; S24- c_inc5, c_inc5_2, Powielenie stanów S3-S19 (tak, że automat wykonuje cztery iteracje S40 c_inc5_3, g_calc5, składające się z inkrementacji liczników, wyliczenia g oraz x_calc5, c_inc6, wyliczenia x); c_inc6_2, g_calc6, x_calc6, c_inc7, c_inc7_2, g_calc7, x_calc7, c_inc8, c_inc8_2, g_calc8, x_calc8, S41 sleeping uśpienie; X_R, C_R, O_R, I_R mają wartość HOLD; V_R to LOAD; SLEEP_OK ma wartość 1; ładowane jest '0' na IV_OK; S42 c_inc_without_iv inkrementacja liczników po raz pierwszy - część pierwsza; FI_S1_R ma wartość LOAD; S43 c_preinc inkrementacja liczników po raz pierwszy – część druga; FI_S2_R ma wartość LOAD; FI_S1_R ma wartość HOLD; S44 c_inc inkrementacja liczników; X_R, O_R mają wartość HOLD, C_DMUX kieruje na inkrementację, FI_REG ma wartość LOAD, C_MUX kieruje z inkrementacji; V_R ma wartość LOAD; C_R to LOAD; S45 g_calc wyliczenie wartości g; C_R, V_R i FI_REG mają wartość HOLD, X_DMUX i C_DMUX kierują na blok wyliczania x, G_R ma wartość LOAD; S46 x_calc_data_take pobranie tekstu i wyliczanie wartości x; X_R to LOAD, X_MUX kieruje z bloku wyliczania x; G_R to HOLD; FI_S1_R, X_R, V_R, I_R, D_R to LOAD; S47 endecrypting generacja strumienia i kodowanie; FI_S1_R, X_R, V_R, I_R, D_R to HOLD; FI_S2_R, D_R i O_R to LOAD; X_DMUX kieruje na blok generacji strumienia; Stany S1 – S20 odpowiadają za pobranie klucza i inicjalizację układu, w szczególności uzyskanie odpowiedniego stanu wewnętrznego; stany S21 – S40 odpowiadają za pobranie i przetworzenie wektora IV; w normalnej pracy (szyfrowanie/deszyfrowanie) automat przyjmuje stany z zakresu S44- S47 oraz stan S41 – uśpienie. Graf stanów z wyszczególnieniem, pod wpływem jakich sygnałów stan się zmienia; jeśli brak wyszczególnienia, rozumieć należy, że stan zmienia się pod wpływem taktu zegara niezależnie od sygnałów na wejściu; sygnał RESET wprowadza automat w stan S0 niezależnie od aktualnego stanu i wartości na wejściach: START [1] S0 S2 S1 S3 S4 S5 START [0] S6 S11 S12 S10 S7 S8 S9 S13 S15 S14 S17 S16 S18 S19 S40 S39 INIT [0] S42 S38 S22 S43 SLEEP [0] SLEEP [1] SLEEP [0] S44 S37 S46 S20 INIT [1] S41 SLEEP [1] S45 S21 S26 INIT [1] S47 S27 S36 S28 S35 S31 S33 S34 S32 S30 S29 Główne zmiany w realizacji względem wersji projektowej: dodano dwa rejestry potokowe przecinające ścieżkę krytyczną w bloku inkrementacji liczników; pozwoliło to zmniejszyć ścieżkę krytyczną o ponad połowę; dzięki wykonywaniu części operacji równolegle nie zwiększono przy tym liczby stanów obsługujących normalną pracę układu; aby obsłużyć rejestry potokowe, dodano 14 nowych stanów; zaimplementowane jest tak, aby wszystkie przypadały na etap przygotowywania układu do pracy, zaś sama praca odbywała się na tej samej liczbie stanów (4 stany); poprawiono sposób obsługi wektora inicjalizującego; teraz wektor inicjalizacyjny można wczytywać wielokrotnie, za każdym razem modyfikując pierwotny stan wewnętrzny układu; ponadto umożliwiono rozpoczęcie korzystania z wektora inicjalizującego w dowolnym momencie pracy układu, a nie jedynie przy ustalaniu stanu wewnętrznego; dodano jednobitowy sygnał wyjścia SLEEP_OK informujący o uśpieniu układu; ujednolicono rolę sygnałów DONE, IV_OK i KEY_OK; ponadto poprzez buforowanie wyjść zlikwidowano szpilki na tych wyjściach; umożliwiło to jednoznacznie zdefiniować sterowanie układu z punktu widzenia interfejsu (dokładne informacje zapisano w dokumentacji pod tabelą interfejsu, strona 4); poprawiono wiele błędów, m.in. błąd podwójnej inkrementacji liczników przy powrocie z uśpienia, błąd zablokowania wyjść rejestrów kopii zapasowej, błąd niezapamiętywania wartości bitu przeniesienia liczników przy zapisywaniu kopii zapasowej; Symulacja dla prostych danych wejściowych (wszystkie liczby zapisane szesnastkowo): wykresy czasowe wygenerowano w Quartusie; widoczne jest następstwo stanów oraz przebieg sygnałów interfejsu; Klucz: 000000000000000000000000 brak IV przedstawiono pierwsze 3 iteracje przy ustalonym stanie wewnętrznym Oczekiwano: B15754F036A5D6ECF56B45261C4AF702 88E8D815C59C0C397B696C4789C68AA7 F416A1C3700CD451DA68D1881673D696 dane oczekiwane wygenerowano za pomocą implementacji szyfru w C dostarczonej przez twórców szyfru; są identyczne z otrzymanym; Klucz: 000000000000000000000000 IV: 000000000000 ustalony stan wewnętrzny, po modyfikacji IV, pierwsze 3 iteracje Oczekiwano: C6A7275EF85495D87CCD5D376705B7ED 5F29A6AC04F5EFD47B8F293270DC4A8D 2ADE822B29DE6C1EE52BDB8A47BF8F66 dane oczekiwane wygenerowano za pomocą implementacji szyfru w C dostarczonej przez twórców szyfru; są identyczne z otrzymanym; Testowanie: Układ testowy napisany został w języku VHDL. Do jego symulacji użyto programu ModelSim 6.1g formy Altera. Układ testowy pobiera wektor klucza 128-bitowego w formacie heksagonalnym z pliku key.txt, podaje go na wejście układu testowanego (a więc implementacji szyfru strumieniowego Rabbit), zdejmuje wraz z odpowiednią wartością na wyjściu KEY_OK, a gdy wyjście IV_OK zasygnalizuje, podaje 64bitowy wektor IV także z pliku key.txt. Następnie zaś pod wpływem wyjścia DONE układu pobiera z pliku test_vectors_iv.txt 3 pierwsze wartości wyjść, i porównuje je z wartościami na wyjściu badanego układu. Jeśli wartości te są identyczne, w pliku results.txt umieszczony zostaje napis „+ Test wykonany poprawnie”, jeśli zaś są błędne, w tym samym pliku zapisane zostaje „- Test wykazal blad”. Wartości wyjść w pliku test_vectors_iv.txt generowane są za pomocą wzorcowej implementacji szyfru strumieniowego Rabbit napisanej w C i dostarczonej przez twórców algorytmu. Kod tej implementacji znaleźć można tutaj: http://www.ecrypt.eu.org/stream/p3ciphers/rabbit/rabbit_p3source.zip Testbench przeprowadzony dla różnych wartości kluczy i wektorów IV wskazywał zawsze na bezbłędną pracę układu. Układ testujący niestety nie zawiera testowania poprawnego działania sygnałów SLEEP i RESET, jednak poprawne działanie tych wejść zaobserwować można na dołączonej do projektu symulacji czasowej w programie Quartus. Wyniki syntezy dla różnych ustawień kompilacji: Syntezę i kompilację przeprowadzono w programie Quartus II wersja 7.2 firmy Altera. Podstawowym układem, dla którego dokonano kompilacji był EP2C35F672C6 z rodziny Cyclone II firmy Altera. Wyniki syntezy przedstawiono poniżej. Kodowanie One-Hot. Synteza Speed: liczba zajętych komórek logicznych: 4 770 częstotliwość zegara: 48,75 MHz Synteza Balanced: liczba zajętych komórek logicznych: 4 769 częstotliwość zegara: 49,59 MHz Synteza Area: liczba zajętych komórek logicznych: 4 777 częstotliwość zegara: 47,16 MHz Kodowanie Minimal Bits: Synteza Balanced: liczba zajętych komórek logicznych: 4 828 częstotliwość zegara: 45,69 MHz Komentarz: Kod okazał się odporny na zabiegi optymalizacyjne na poziomie typu syntezy, co nie jest w żadnym stopniu zaskoczeniem. Częstotliwość zegara jest w pełni określona przez długość ścieżki krytycznej, której przebycie w naszej implementacji szyfru na układzie EP2C35F672C6 zawsze oscylowało wokół 20 ns (czas ustalenia się wartości w łańcuchu sumatorów w module increment lub czas wykonania podniesienia do kwadratu liczby 32bitowej w module nextstate). Wielkość układu (ilość komórek) w pewnym stopniu określało zapotrzebowanie na rejestry (1591 bitów przy kodowaniu one-hot), zaś największą możliwość optymalizacji zostawiał moduł nextstate, w którym podnoszenie do kwadratu liczby 32bitowej zajmowało bardzo wiele komórek. Zajętość ta najwyraźniej jest w układzie EP2C35F672C6 niezbędna, gdyż nie udało się tego modułu zmniejszyć. Jak pokazały wyniki syntezy, algorytmy zaimplementowane w Quartusie niezbyt skutecznie wykonały swe zadanie – synteza typu Area zajęła największą liczbę komórek logicznych, zaś Speed jest wolniejszy niż Balanced. Synteza Balanced okazała się ogólnie najlepszą, zarówno pod względem zajętości układu, jak i szybkości, jednak różnice są niewielkie. Zastosowanie kodowania Minimal Bits również nie wpłynęło znacząco na układ – liczba zajętych komórek nieco się zwiększyła (choć zmniejszyła się liczba zajętych komórek rejestrów, z 1591 na 1549, co potwierdza rzeczywiste zmniejszenie liczby bitów kodujących stany automatu), prędkość nieco spadła. Trzeba jednak podkreślić, że wahania prędkości wynikały z zastosowanych w danej syntezie algorytmów rozmieszczania w układzie poszczególnych komórek, które czasem nieco zmieniały długość ścieżki krytycznej, ale nie wpływały na nią szczególnie – wykonanie danej operacji (sumowania kaskadowego w module increment) na danej liczbie bitów najwyraźniej w układzie EP2C35F672C6 zajmuje zawsze podobny czas. Drogą do optymalizacji jest więc nie rodzaj syntezy, a cięcie ścieżki krytycznej. Uwagi końcowe, wnioski, przemyślenia własne zespołu projektowego: Kod implementacji szyfru strumieniowego Rabbit napisany przez nasz zespół projektowy jest kodem względnie uniwersalnym, krok po kroku realizującym algorytm szyfrowania. Oznacza to, że zmodyfikowanie kodu tak, by układ pracował w danych warunkach dotyczących np. szerokości szyn w interfejsie albo wymaganej przepływności bitowej szyfrowania, nie powinno nastręczać wielu trudności. Oczywiście za prostotę i uniwersalność kodu płaci się powolnym działaniem. Wąskim gardłem układu są dwa moduły – increment z łańcuchem kaskadowym sumatorów 32bitowych oraz nextstate z wyliczaniem kwadratu liczby 32bitowej. O ile zmniejszenie ścieżki krytycznej na łańcuchu sumatorów oznacza proste przecinanie ścieżek przenoszących bit carry rejestrami potokowymi (co pozwoli ok. 2-3 krotnie przyspieszyć zegar), o tyle mnożenie 32bitowych liczb wymagać będzie albo zastosowania jakiegoś algorytmu mnożenia, albo wyspecjalizowanych komórek mnożących. Układ EP2C35F672C6 posiada 70 takich komórek, jednak operują one na max. 9 bitach. Jeśli udałoby się zdekomponować mnożenie 32bitów na bloki 9-bitowe, układ znacząco by przyspieszył. Pamiętać jednak należy, że każdy rejestr potokowy oznacza jeden więcej stan automatu w czasie rzeczywistej pracy. Oczywiście na etapie inicjalizacji układu dodanie nawet bardzo znacznej liczby stanów nie powinno mieć większych konsekwencji – objawi się to jedynie wolniejszym startowaniem, co dla układów szyfrujących zwykle nie jest najważniejsze, natomiast w czasie normalnej pracy liczba stanów przy określonej częstotliwości zegara determinuje przepustowość bitową, zgodnie ze wzorem: przepustowość bitowa [bit/sec] = 128 [bit] * częstotliwość [Hz] / liczba stanów automatu w pracy Liczba 128 w tym wzorze to wielkość bloku, na jakim operuje szyfr Rabbit i wynika bezpośrednio z algorytmu dostarczonego przez twórców szyfru. W obecnym kodzie przepustowość wynosi ok. 1,6 Gbit/sec. Liczba stanów automatu w czasie pracy to 4, częstotliwość ok. 50MHz. Trzeba jednak zaznaczyć, że wielkość ta oznacza w rzeczywistości wydajność układu, jeśli zapewnimy podawanie na wejście tak dużej liczby danych, i to w porcjach po 128 bitów. Wymagania takie w wielu przypadkach mogą nie pasować do zastosowania, z czego zdajemy sobie sprawę. Szczególnie istotny jest z naszego punktu widzenia problem wielkości szyn w interfejsie; 2x128 bitów to stosunkowo wiele (płytka laboratoryjna zapewnia komunikację przez USB z modułem odczytującym naraz jedynie 16 bitów!). Oczywiście nic nie stoi na przeszkodzie, żeby liczbę tę zmniejszyć; trzeba będzie jednak zwiększyć wówczas liczbę stanów (gdyż obecny kod nie umożliwia zmniejszenia szyny do mniej niż 40 bitów) i dodać wyjście w interfejsie informujące o pobraniu paczki danych. Zwiększenie liczby stanów ze względu na wąską szynę wejściową będzie miało też dobre strony – równolegle z pobieraniem danych wykonywać będzie można operacje inkrementujące stan automatu, czyli dokonać wspomnianej wyżej optymalizacji modułów increment i nextstate. Zmniejszenie ścieżki krytycznej złagodzi zmianę liczby stanów i związany z nią spadek przepustowości. Oczywiście może też zdarzyć się tak, że przed układem postawione będą inne warunki pracy, związane ze stałą częstotliwością zegara albo z koniecznością korzystania z jednej szyny w trybie write/read. Trzeba będzie wówczas wprowadzić w kod niezbędne zmiany; ich charakter zależy jednak od konkretnej sytuacji, dlatego trudno podać jest ogólny algorytm optymalizacyjny. Najistotniejsze z naszego punktu widzenia jest zachowanie równowagi między zwiększaniem szybkości a zwiększaniem liczby stanów automatu, oraz wykorzystywanie wszelkich możliwości wykonywania operacji równolegle. Twórcy algorytmu szacują, że dobrze zaprojektowana implementacja powinna działać na układach o stopniu zaawansowania porównywalnym z EP2C35F672C6 z przepływnością sięgającą 9 Gbit/sec. Nasz układ działa przeszło 5 razy wolniej, więc zdecydowanie jest jeszcze nad czym pracować, pamiętając jednak, że to warunki zewnętrzne pracy układu powinny warunkować ostateczną strategię optymalizacji.