В этом документе формально определяется математическая функция, представленная ключами потоковой передачи 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, отклоняются.
Ссылки
[HRRV15] Хоанг, Рейханитабар, Рогавей, Визар. Онлайн-шифрование с аутентификацией и его устойчивость к неправильному использованию при повторном использовании. КРИПТО 2015. https://eprint.iacr.org/2015/189 ↩
[HS20] Безопасность потокового шифрования в библиотеке Google Tink. Хоанг, Шен, 2020 г. https://eprint.iacr.org/2020/1019 ↩
[HKDF] Функция извлечения и расширения ключей на основе HMAC (HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩