AES-CTR Потоковая передача HMAC AEAD

В этом документе формально определяется математическая функция, представленная ключами потоковой передачи AES-CTR HMAC (закодированными в прототипном формате как type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey ).

Это шифрование во многом основано на [HRRV15] 1 . Для анализа безопасности мы обращаемся к [HS20] 2 . Также обратите внимание, что кросс-языковые тесты Tink имеют тест aes_ctr_hmac_streaming_key_test.py , который содержит test_manually_created_test_vector с полным описанием того, как получить зашифрованный текст.

Ключ и параметры

Ключи описываются следующими частями (все размеры в этом документе указаны в байтах):

  • \(\mathrm{InitialKeyMaterial}\)— байтовая строка: исходный ключевой материал.
  • \(\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}\).

Действительные ключи дополнительно удовлетворяют следующим свойствам:

  • \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\).
  • Если \(\mathrm{HmacHashType} = \mathrm{SHA1}\) , то \(\mathrm{HmacTagSize} \in \{10, \ldots, 20\}\).
  • Если \(\mathrm{HmacHashType} = \mathrm{SHA256}\) , то \(\mathrm{HmacTagSize} \in \{10, \ldots, 32\}\).
  • Если \(\mathrm{HmacHashType} = \mathrm{SHA512}\) , то \(\mathrm{HmacTagSize} \in \{10, \ldots, 64\}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + \mathrm{HmacTagSize} + 8\) (это эквивалентно\(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) , как объяснено позже).

Ключи, которые не удовлетворяют ни одному из этих свойств, отклоняются Tink (либо при анализе ключа, либо при создании соответствующего примитива).

Функция шифрования

Чтобы зашифровать сообщение \(\mathrm{Msg}\) с соответствующими данными\(\mathrm{AssociatedData}\), мы создаем заголовок, разбиваем сообщение на сегменты, шифруем каждый сегмент и объединяем сегменты. Мы объясним эти шаги ниже.

Создание заголовка

Чтобы создать заголовок, мы сначала выбираем однородную случайную строку \(\mathrm{Salt}\)длины \(\mathrm{DerivedKeySize}\). Затем мы выбираем однородную случайную строку\(\mathrm{NoncePrefix}\) длины 7.

Затем мы устанавливаем\(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), где длина заголовка кодируется как один байт. Обратите внимание, что\(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Далее мы используем HKDF 3 с хеш-функцией \(\mathrm{HkdfHashType}\)для вычисления ключевого материала длиной\(\mathrm{DerivedKeySize} + 32\) для этого сообщения:\(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). Входные данные используются в соответствующих входных данных\(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) — это \(\mathrm{ikm}\),\(\mathrm{Salt}\) — соль, а\(\mathrm{AssociatedData}\) используется как \(\mathrm{info}\).

Затем строка \(k\) разбивается на две части \(k_1 \| k_2 := k\), например,\(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) и \(\mathrm{len}(k_2) = 32\).

Разделение сообщения

Сообщение \(M\) далее разбивается на части: \(M = M_0 \| M_1 \| \cdots \| M_{n-1}\).

Их длины выбираются так, чтобы они удовлетворяли:

  • \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{HmacTagSize}\}\).
  • Если \(n > 1\), то \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\).
  • Если \(n > 1\), то \(M_{0}, \ldots, M_{n-2}\) должен иметь максимальную длину в соответствии с указанными выше ограничениями.

При таком разбиении \(n\) может быть максимум \(2^{32}\). В противном случае шифрование не удастся.

Шифрование блоков

Чтобы зашифровать сегмент \(M_i\), мы сначала вычисляем\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\), где мы кодируем \(\mathrm{i}\) в 4 байтах, используя кодировку с прямым порядком байтов, и устанавливаем байт $b$ равным 0x00 если $i < n-1$, и 0x01 в противном случае. .

Затем мы шифруем \(M_i\) с использованием ключа AES CTR \(k_1\)и вектора инициализации\(\mathrm{IV}_i\). Другими словами, входными данными для вызовов AES являются\(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\), где \(\mathrm{IV}_i\) интерпретируется как целое число с прямым порядком байтов. Это дает \(C'_i\).

Мы вычисляем тег, используя HMAC, с хэш-функцией, заданной \(\mathrm{HmacHashType}\) , и с ключом \(k_2\) через конкатенацию\(\mathrm{IV}_i \| C'_i\).

Затем мы объединяем зашифрованный текст, за которым следует тег, чтобы получить \(C_i\).

Объедините сегменты

Наконец, все сегменты объединяются в\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), который является окончательным зашифрованным текстом.

Функция расшифровки

Расшифровка просто инвертирует шифрование. Мы используем заголовок для получения nonce и расшифровываем каждый сегмент зашифрованного текста индивидуально.

API-интерфейсы могут (и обычно так и делают) разрешать произвольный доступ или доступ к началу файла без проверки конца файла. Это сделано намеренно, поскольку можно расшифровать \(M_i\) из \(C_i\), не расшифровывая все предыдущие и оставшиеся блоки зашифрованного текста.

Однако API должны быть осторожны, чтобы не позволить пользователям путать ошибки конца файла и ошибки расшифровки: в обоих случаях API, вероятно, должен вернуть ошибку, и игнорирование разницы может привести к тому, что злоумышленник сможет эффективно обрезать файлы.

Сериализация и парсинг ключей

Чтобы сериализовать ключ в формате «Tink Proto», мы сначала сопоставляем параметры очевидным образом с прототипом, указанным в aes_ctr_hmac_streaming.proto . version поля должна быть установлена ​​на 0. Затем мы сериализуем ее, используя обычную сериализацию прототипа, и встраиваем полученную строку в значение поля прототипа KeyData . Мы устанавливаем для поля type_url type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey . Затем мы устанавливаем key_material_type в SYMMETRIC и встраиваем его в набор ключей. Обычно мы устанавливаем для параметра output_prefix_type значение RAW . Исключением является то, что если ключ был проанализирован с другим значением, установленным для output_prefix_type , Tink может записать либо RAW , либо предыдущее значение.

Чтобы разобрать ключ, мы обращаем описанный выше процесс в обратном порядке (обычным способом при разборе прото). Поле key_material_type игнорируется. Значение output_prefix_type можно либо игнорировать, либо ключи, у которых output_prefix_type отличается от RAW , могут быть отклонены. Ключи, version которых отличается от 0, отклоняются.

Рекомендации


  1. [HRRV15] Хоанг, Рейханитабар, Рогавей, Визар. Онлайн-шифрование с аутентификацией и его устойчивость к неправильному использованию при повторном использовании. КРИПТО 2015. https://eprint.iacr.org/2015/189

  2. [HS20] Безопасность потокового шифрования в библиотеке Google Tink. Хоанг, Шен, 2020 г. https://eprint.iacr.org/2020/1019

  3. [HKDF] Функция извлечения и расширения ключей на основе HMAC (HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869