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