AES-GCM-HKDF strumieniowanie AEAD

Ten dokument oficjalnie określa funkcję matematyczną reprezentowaną przez klucze strumienia AES-GCM-HKDF (zakodowane w formacie proto jako type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey).

To szyfrowanie jest luźno oparte na [HRRV15]1. Analizę zabezpieczeń znajdziesz tutaj: [HS20]2.

Klucz i parametry

Klucze są opisane w tych częściach (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ą dodatkowo te wymagania:

  • \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (Jest to równa\(\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 klucza lub po utworzeniu odpowiedniego elementu podstawowego).

Funkcja szyfrowania

Aby zaszyfrować wiadomość \(\mathrm{Msg}\) wraz z powiązanymi danymi\(\mathrm{AssociatedData}\), tworzymy nagłówek, dzielimy wiadomość na segmenty, szyfrujemy każdy z nich i scalamy segmenty. Poniżej objaśniamy te kroki.

Tworzenie nagłówka

Aby utworzyć nagłówek, najpierw wybieramy jednolity losowy ciąg znaków \(\mathrm{Salt}\)o długości \(\mathrm{DerivedKeySize}\). Następnie wybieramy 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 kodowana jako pojedynczy bajt. Zwróć uwagę, że\(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Następnie używamy HKDF3 z funkcją skrótu określoną przez \(\mathrm{HkdfHashType}\)oraz dane wejściowe \(\mathrm{ikm} := \mathrm{KeyValue}\),\(\mathrm{salt} := \mathrm{Salt}\)i\(\mathrm{info} := \mathrm{AssociatedData}\)z długością wyjściową \(\mathrm{DerivedKeySize}\). Wynik ten nazywamy \(k\).

Dzielenie wiadomości

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

Ich długości są tak wybierane, by spełniały te wymagania:

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

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

Szyfrowanie bloków

Aby zaszyfrować segment \(M_i\), najpierw obliczamy wartość\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b\), w której kodujemy\(\mathrm{i}\) w 4 bajtach z użyciem kodowania big-endian. W polu $b$ ustawiamy wartość 0x00, jeśli $i < n-1$, a 0x01 w innych przypadkach.

Następnie szyfrujemy \(M_i\) za pomocą AES GCM4, gdzie klucz to\(\mathrm{DerivedKey}\), wektor inicjujący to \(\mathrm{IV}_i\), a powiązane dane \(A\) to pusty ciąg znaków. Ustaliliśmy, że ma to być \(C_i\) wynik tego szyfrowania (czyli konkatenacja \(C\) i \(T\) opisanej powyżej).

Połącz segmenty

Na koniec wszystkie segmenty są połączone jako\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), który jest ostatecznym tekstem szyfrowanym.

Odszyfrowywanie

Odszyfrowywanie tylko zapobiega odszyfrowaniu. Uzyskujemy liczbę jednorazową i odszyfrowujemy każdy segment tekstu szyfrowania z osobna.

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

W interfejsach API należy jednak zadbać o to, aby nie mylić błędów na końcu pliku i odszyfrowywania – w obu przypadkach API prawdopodobnie musi zwrócić błąd, a ignorowanie różnicy może sprawić, że przeciwnik będzie mógł skutecznie skrócić pliki.

Serializacja i analiza kluczy

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

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

Znane problemy

Implementacje powyższej funkcji szyfrowania nie powinny być bezpieczne. Zobacz Bezpieczeństwo rozwidleń.

Odniesienia


  1. [HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Uwierzytelnione szyfrowanie online i odporność na nadużycia związane z ponownym użyciem. CRYPTO 2015. https://eprint.iacr.org/2015/189 

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

  3. [HKDF] Funkcja wyodrębniania klucza i rozwijanie na podstawie HMAC (HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 

  4. NIST SP 800-38D, rekomendacja dla trybów blokowych szyfrowania: galois/licznika (GCM) i GMAC.