AES-GCM-HKDF strumieniowanie AEAD

Ten dokument formalnie definiuje funkcję matematyczną reprezentowaną przez klucze strumieniowe AES-GCM-HKDF zakodowane w formacie proto jako type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey.

Jest ono luźno oparte na HRRV151. W przypadku analizy zabezpieczeń odwołujemy się do HS202.

Klucz i parametry

Klucze są opisane za pomocą tych części (wszystkie rozmiary w tym dokumencie są podawane w bajtach):

  • \(\mathrm{KeyValue}\), ciąg bajtów.
  • \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\).
  • \(\mathrm{DerivedKeySize} \in \{16, 32\}\).
  • \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).

Prawidłowe klucze muszą też spełniać te właściwości:

  • \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (jak wyjaśniono dalej, jest to równe \(\mathrm{len}(\mathrm{Header}) + 16\) ).

Klucze, które nie spełniają żadnej z tych właściwości, są odrzucane przez Tink, gdy klucz jest analizowany lub gdy tworzony jest odpowiedni prymityw.

Funkcja szyfrowania

Aby zaszyfrować wiadomość \(\mathrm{Msg}\) z powiązanymi danymi,\(\mathrm{AssociatedData}\)tworzymy nagłówek, dzielimy wiadomość na segmenty, szyfrujemy każdy segment i konkatenujemy zaszyfrowane segmenty.

Tworzenie nagłówka

Wybieramy losowy ciąg znaków o jednolitej długości \(\mathrm{Salt}\) \(\mathrm{DerivedKeySize}\) i losowy ciąg znaków o jednolitej długości \(\mathrm{NoncePrefix}\)7.

Następnie ustawiamy wartość \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), gdzie długość nagłówka jest kodowana jako pojedynczy bajt. Pamiętaj, że \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Następnie używamy funkcji HKDF3 z funkcją szyfrowania określoną przez parametr \(\mathrm{HkdfHashType}\)oraz wejść \(\mathrm{ikm} := \mathrm{KeyValue}\), \(\mathrm{salt} := \mathrm{Salt}\)i  \(\mathrm{info} := \mathrm{AssociatedData}\), z długością wyjścia \(\mathrm{DerivedKeySize}\). Nazywamy to wynikiem \(\mathrm{DerivedKey}\).

Podziel wiadomość

Następnie wiadomość \(\mathrm{Msg}\) zostaje podzielona na części: \(\mathrm{Msg} = M_0 \| M_1 \| \cdots \| M_{n-1}\).

Ich długości są dobierane tak, aby spełniać te kryteria:

  • \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{16}\}\).
  • Jeśli \(n>1\), to \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{16}\}\).
  • Jeśli ustawiona jest wartość \(n>1\), pole \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) musi mieć maksymalną długość zgodnie z powyższymi ograniczeniami.

Wartość\(n\) nie może przekraczać \(2^{32}\). W przeciwnym razie szyfrowanie się nie powiedzie.

Szyfrowanie bloków

Aby zaszyfrować segment \(M_i\), obliczamy wartość \(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b\), gdzie \(\mathrm{i}\) to 4 bajty w kodowaniu big-endian, a bajt $b$ to 0x00, jeśli $i < n-1$, i 0x01 w przeciwnym razie.

Następnie szyfrujemy \(M_i\) za pomocą AES-GCM4, gdzie kluczem jest\(\mathrm{DerivedKey}\), wektorem inicjującym jest \(\mathrm{IV}_i\), a powiązane dane to pusty ciąg znaków. \(C_i\) to wynik tego szyfrowania (czyli konkatenacja wartości \(C\) i \(T\) w sekcji 5.2.1.2 w powiązanym dokumencie na temat AES-GCM).

Łączenie zaszyfrowanych segmentów

Na koniec wszystkie segmenty są łączone w postaci \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), która jest ostatecznym tekstem zaszyfrowanym.

Odszyfrowywanie

Odszyfrowanie odwraca szyfrowanie. Używamy nagłówka do uzyskania \(\mathrm{NoncePrefix}\), a następnie odszyfrowujemy każdy segment tekstu zaszyfrowanego osobno.

Interfejsy API mogą (i zwykle tak jest) zezwalać na dostęp losowy lub dostęp do początku pliku bez sprawdzania jego końca. Jest to celowe działanie, ponieważ można odszyfrować \(M_i\) z \(C_i\)bez odszyfrowywania wszystkich poprzednich i pozostałych bloków szyfratu.

Interfejsy API powinny jednak zapobiegać myleniu przez użytkowników błędów końca pliku i błędów odszyfrowywania. W obu przypadkach interfejs API musi zwrócić błąd, a ignorowanie różnicy może pozwolić przeciwnikowi skutecznie obcinać pliki.

Serializacja i parsowanie kluczy

Aby zakodować klucz w formacie „Tink Proto”, najpierw mapujemy parametry w oczywisty sposób na prototyp podany w pliku aes_gcm_hkdf_streaming.proto. Pole version musi mieć wartość 0. Następnie serializujemy to za pomocą zwykłej serializacji proto i umieszczamy uzyskany ciąg znaków w polu wartości proto KeyData. W polu type_url ustawiliśmy wartość type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey. Następnie ustawiamy key_material_type na SYMMETRIC i osadzamy to w kluczu. Zwykle ustawiamy output_prefix_type na RAW. Wyjątkiem jest sytuacja, gdy klucz został przeanalizowany z inną wartością dla parametru output_prefix_type. W takim przypadku Tink może zapisać wartość RAW lub poprzednią.

Aby przeanalizować klucz, odwracamy powyższy proces (w zwykły sposób podczas analizowania protosów). Pole key_material_type jest ignorowane. Wartość parametru output_prefix_type może zostać zignorowana, a klucze, w których parametr output_prefix_type ma wartość inną niż RAW, mogą zostać odrzucone. Klucze, których wartość parametru version jest inna niż 0, należy odrzucić.

Znane problemy

Implementacje tej funkcji szyfrowania nie powinny być bezpieczne w przypadku rozgałęzienia. Zobacz Fork Safety.

Pliki referencyjne


  1. Hoang, Reyhanitabar, Rogaway, Vizar, 2015. szyfrowanie z uwierzytelnianiem online oraz odporność na ponowne użycie nonce; CRYPTO 2015. https://eprint.iacr.org/2015/189 

  2. Hoang, Shen, 2020. Bezpieczeństwo szyfrowania strumieniowego w bibliotece Google Tinkoff. https://eprint.iacr.org/2020/1019 

  3. RFC 5869. Funkcja wyodrębniania i rozwijania klucza (HKDF) oparta na HMAC. https://www.rfc-editor.org/rfc/rfc5869 

  4. NIST SP 800-38D. Zalecane tryby działania szyfrów blokowych: Galois/Counter Mode (GCM) i GMAC. https://csrc.nist.gov/pubs/sp/800/38/d/final