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.

Podobne dokumenty