AES-GCM-HKDF strumieniowanie AEAD

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

To szyfrowanie jest luźno oparte na standardzie HRRV151. Informacje na temat analizy zabezpieczeń można znaleźć w normie HS202.

Klucz i parametry

Klucze są opisane w następujący sposób (wszystkie rozmiary w tym dokumencie są podane 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 spełniają też te warunki:

  • \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (równa się \(\mathrm{len}(\mathrm{Header}) + 16\) , jak wyjaśnimy później).

Klucze, które nie spełniają żadnej z tych właściwości, są odrzucane przez Tink podczas analizy lub tworzenia odpowiedniego elementu podstawowego.

Funkcja szyfrowania

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

Tworzenie nagłówka

Wybieramy jednolity, losowy ciąg \(\mathrm{Salt}\) o długości\(\mathrm{DerivedKeySize}\) i jednolity losowy ciąg \(\mathrm{NoncePrefix}\)o długości 7.

Następnie ustawiamy \(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), gdzie długość nagłówka jest zakodowana jako 1 bajt. Uwaga: \(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Następnie używamy HKDF3 z funkcją haszującą podaną przez \(\mathrm{HkdfHashType}\)i dane wejściowe \(\mathrm{ikm} := \mathrm{KeyValue}\), \(\mathrm{salt} := \mathrm{Salt}\)i \(\mathrm{info} := \mathrm{AssociatedData}\)z długością danych wyjściowych \(\mathrm{DerivedKeySize}\). Wynik określamy jako \(\mathrm{DerivedKey}\).

Podziel wiadomość

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

Ich długości są dobierane w taki sposób, aby spełniały:

  • \(\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 \(n>1\), \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) muszą mieć maksymalną długość zgodnie z powyższymi ograniczeniami.

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

Zaszyfruj bloki

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

Następnie zaszyfrujemy \(M_i\) przy użyciu AES-GCM4, gdzie klucz to\(\mathrm{DerivedKey}\), wektor inicjujący to \(\mathrm{IV}_i\), a powiązane dane to pusty ciąg znaków. \(C_i\) Jest to wynikiem tego szyfrowania (tj. połączenie \(C\) i \(T\) powiązanych plików referencyjnych AES-GCM).

Połącz zaszyfrowane segmenty

Na koniec wszystkie segmenty są łączone w \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), czyli ostateczny tekst szyfrowany.

Odszyfrowywanie

Odszyfrowywanie odwraca szyfrowanie. Wykorzystujemy go, aby uzyskać \mathrm{NoncePrefix}$$ i osobno odszyfrować każdy segment tekstu zaszyfrowanego.

Interfejsy API mogą – i zwykle to umożliwiają – dostęp losowy lub dostęp do początku pliku bez sprawdzania jego końca. Jest to celowe, ponieważ odszyfrowanie \(M_i\) z \(C_i\)bez odszyfrowywania wszystkich poprzednich i pozostałych bloków zaszyfrowanego tekstu.

Interfejsy API powinny jednak uważać, aby nie pozwalać użytkownikom pomylić błędów na końcu pliku z błędami odszyfrowywania: w obu przypadkach interfejs API prawdopodobnie musi zwrócić błąd, a zignorowanie różnicy może sprawić, że przeciwnik będzie mógł skutecznie przyciąć pliki.

Serializacja i analiza kluczy

Aby zserializować klucz w formacie „Tink Proto”, najpierw zmapujemy parametry w oczywisty sposób na proto podane w aes_gcm_hkdf_streaming.proto. Pole version musi mieć wartość 0. Następnie zserializujemy go za pomocą normalnej serializacji proto i umieszczamy wynikowy ciąg w wartości pola KeyData. W polu type_url ustawiliśmy wartość type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey. Następnie ustawiliśmy key_material_type na SYMMETRIC i umieściliśmy go w zestawie kluczy. W polu output_prefix_type zwykle ustawiamy wartość RAW. Wyjątkiem jest to, że jeśli klucz został analizowany z inną wartością ustawioną dla output_prefix_type, Tink może zapisać RAW lub poprzednią wartość.

Aby przeanalizować klucz, zwracamy poprzedni proces (w zwykły sposób podczas analizowania proto). Pole key_material_type jest ignorowane. Wartość output_prefix_type może być ignorowana lub klucze, które różnią się wartością output_prefix_type od RAW, mogą być odrzucane. Klucze, których wartość version różni się od 0, muszą zostać odrzucone.

Znane problemy

Implementacje tej funkcji szyfrowania nie powinny być bezpieczne do tworzenia rozwidlenia. Zobacz Bezpieczeństwo widelec.

Źródła


  1. Hoang, Reyhanitabar, Rogaway, Vizar, 2015 r. Uwierzytelnianie uwierzytelnione w internecie i ich odporność na nadużycia CRYPTO 2015. https://eprint.iacr.org/2015/189 

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

  3. RFC 5869. Oparta na HMAC funkcja derywacji kluczy wyodrębniania i rozwijania (HKDF). https://www.rfc-editor.org/rfc/rfc5869 

  4. NIST SP 800-38D. Zalecenia dotyczące trybów szyfrowania blokowego: Galois/licznik licznika (GCM) i GMAC. https://csrc.nist.gov/pubs/sp/800/38/d/final