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
-
[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 ↩
-
[HS20] Bezpieczeństwo szyfrowania strumieniowania w bibliotece Tink Google Hoang, Shen, 2020 r. https://eprint.iacr.org/2020/1019 ↩
-
[HKDF] Funkcja wyodrębniania klucza i rozwijanie na podstawie HMAC (HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩
-
NIST SP 800-38D, rekomendacja dla trybów blokowych szyfrowania: galois/licznika (GCM) i GMAC. ↩