Strumieniowe przesyłanie AEAD HMAC AES-CTR

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

To szyfrowanie jest luźno oparte na [HRRV15]1. Analizę zabezpieczeń znajdziesz tutaj: [HS20]2. Pamiętaj też, że testy w różnych językach Tink obejmują test aes_ctr_hmac_streaming_key_test.py, który zawiera test_manually_created_test_vector z kompletnym instrukcją pobierania tekstu szyfrowania.

Klucz i parametry

Klucze są opisane w tych częściach (wszystkie rozmiary w tym dokumencie są podane w bajtach):

  • \(\mathrm{InitialKeyMaterial}\), ciąg bajtów: początkowy materiał klucza.
  • \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\).
  • \(\mathrm{DerivedKeySize} \in \{16, 32\}\).
  • \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
  • \(\mathrm{HmacHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).
  • \(\mathrm{HmacTagSize} \in \mathbb{N}\).

Prawidłowe klucze spełniają dodatkowo te wymagania:

  • \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\).
  • Jeśli \(\mathrm{HmacHashType} = \mathrm{SHA1}\) to \(\mathrm{HmacTagSize} \in \{10, \ldots, 20\}\).
  • Jeśli \(\mathrm{HmacHashType} = \mathrm{SHA256}\) to \(\mathrm{HmacTagSize} \in \{10, \ldots, 32\}\).
  • Jeśli \(\mathrm{HmacHashType} = \mathrm{SHA512}\) to \(\mathrm{HmacTagSize} \in \{10, \ldots, 64\}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + \mathrm{HmacTagSize} + 8\) (Jest to równa\(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) , 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 podczas tworzenia 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 \(\mathrm{HkdfHashType}\), aby obliczyć materiał klucza o długości\(\mathrm{DerivedKeySize} + 32\) dla tej wiadomości:\(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). Dane wejściowe są używane w odpowiednich danych wejściowych funkcji\(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) to \(\mathrm{ikm}\),\(\mathrm{Salt}\) to sól i\(\mathrm{AssociatedData}\) są używane jako \(\mathrm{info}\).

Następnie \(k\) dzieli się on na 2 części \(k_1 \| k_2 := k\), np.\(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) i \(\mathrm{len}(k_2) = 32\).

Dzielenie wiadomości

Wiadomość \(M\) została podzielona na części: \(M = 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{HmacTagSize}\}\).
  • Jeśli \(n > 1\), to \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\).
  • Jeśli argument \(n > 1\), \(M_{0}, \ldots, M_{n-2}\) musi mieć maksymalną długość zgodnie 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 \| 0x00000000\), w której kodujemy \(\mathrm{i}\) 4 bajty przy użyciu kodowania big-endian. Jeśli w innym przypadku argument $i < n-1$ to 0x01, ustawiamy wartość 0x00 na 0x00.

Następnie szyfrujemy \(M_i\) przy użyciu klucza CTR AES \(k_1\)i wektora inicjującego\(\mathrm{IV}_i\). Inaczej mówiąc, dane wejściowe do wywołań AES są\(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\) gdzie \(\mathrm{IV}_i\) są interpretowane jako liczba całkowita typu big-endian. Daje to \(C'_i\).

Obliczamy tag za pomocą HMAC z funkcją skrótu określoną przez \(\mathrm{HmacHashType}\) i klucz \(k_2\) przez konkatenację\(\mathrm{IV}_i \| C'_i\).

Następnie łączymy tekst szyfrowania z tagiem, aby uzyskać \(C_i\).

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.

Funkcja odszyfrowywania

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_ctr_hmac_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.AesCtrHmacStreamingKey. 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óre mają wartość version inną niż 0, są odrzucane.

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