En este documento, se define formalmente la función matemática representada por las claves de transmisión de HMAC AES-CTR (codificadas en formato proto como type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey
).
Esta encriptación se basa de forma vaga en [HRRV15]1. Para un análisis de la seguridad, consulta [HS20]2. También ten en cuenta que las pruebas de Tink en varios idiomas tienen una prueba aes_ctr_hmac_streaming_key_test.py que contiene test_manually_created_test_vector
con una explicación completa sobre cómo obtener un texto cifrado.
Clave y parámetros
Las claves se describen con las siguientes partes (todos los tamaños en este documento se indican en bytes):
- \(\mathrm{InitialKeyMaterial}\), una cadena de bytes: el material de clave inicial.
- \(\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}\).
Además, las claves válidas cumplen con las siguientes propiedades:
- \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\).
- Si \(\mathrm{HmacHashType} = \mathrm{SHA1}\) , entonces \(\mathrm{HmacTagSize} \in \{10, \ldots, 20\}\).
- Si \(\mathrm{HmacHashType} = \mathrm{SHA256}\) , entonces \(\mathrm{HmacTagSize} \in \{10, \ldots, 32\}\).
- Si \(\mathrm{HmacHashType} = \mathrm{SHA512}\) , entonces \(\mathrm{HmacTagSize} \in \{10, \ldots, 64\}\).
- \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + \mathrm{HmacTagSize} + 8\) (esto es igual a\(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) , como se explica más adelante).
Tink rechaza las claves que no satisfacen ninguna de estas propiedades (ya sea cuando se analiza la clave o cuando se crea la primitiva correspondiente).
Función de encriptación
Para encriptar un mensaje \(\mathrm{Msg}\) con datos asociados\(\mathrm{AssociatedData}\), creamos un encabezado, dividimos el mensaje en segmentos, encriptamos cada uno y los concatenamos. A continuación, te explicamos estos pasos.
Crea el encabezado
Para crear el encabezado, primero elegimos una cadena aleatoria uniforme \(\mathrm{Salt}\) de longitud \(\mathrm{DerivedKeySize}\). A continuación, elegimos una cadena aleatoria uniforme \(\mathrm{NoncePrefix}\) de 7 caracteres.
Luego, configuramos\(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), en la que la longitud del encabezado se codifica como un solo byte. Notamos que\(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).
A continuación, usamos HKDF3 con la función hash \(\mathrm{HkdfHashType}\)para calcular el material de clave de longitud\(\mathrm{DerivedKeySize} + 32\) para este mensaje:\(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). Las entradas se usan en las entradas correspondientes de\(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) es \(\mathrm{ikm}\),\(\mathrm{Salt}\) es la sal y\(\mathrm{AssociatedData}\) se usa como \(\mathrm{info}\).
Luego, la cadena \(k\) se divide en dos partes \(k_1 \| k_2 := k\), de modo que\(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) y \(\mathrm{len}(k_2) = 32\).
Cómo dividir el mensaje
A continuación, el mensaje \(M\) se divide en partes: \(M = M_0 \| M_1 \| \cdots \| M_{n-1}\).
Sus longitudes se eligen para que cumplan con los siguientes requisitos:
- \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{HmacTagSize}\}\).
- Si \(n > 1\), entonces \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\).
- Si \(n > 1\), entonces \(M_{0}, \ldots, M_{n-2}\) debe tener una longitud máxima según las restricciones anteriores.
En esta división, \(n\) puede ser como máximo \(2^{32}\). De lo contrario, la encriptación fallará.
Encriptación de los bloques
Para encriptar el segmento \(M_i\), primero calculamos\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\), en el que codificamos \(\mathrm{i}\) en 4 bytes con codificación de números grandes y establecemos el byte $b$ como 0x00
si $i < n-1$ y 0x01
de lo contrario.
Luego, encriptamos \(M_i\) con la clave AES CTR \(k_1\)y el vector de inicialización\(\mathrm{IV}_i\). En otras palabras, las entradas de las invocaciones de AES son\(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\), donde \(\mathrm{IV}_i\) se interpreta como un número entero de formato big-endian. Esto genera \(C'_i\).
Calculamos la etiqueta con HMAC con la función hash que proporciona \(\mathrm{HmacHashType}\) y con la clave \(k_2\) sobre la concatenación\(\mathrm{IV}_i \| C'_i\).
Luego, concatenamos el texto cifrado seguido de la etiqueta para obtener \(C_i\).
Concatena los segmentos
Por último, todos los segmentos se concatenan como\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), que es el texto cifrado final.
Función de desencriptación
La desencriptación simplemente invierte la encriptación. Usamos el encabezado para obtener el nonce y desencriptar cada segmento de texto cifrado de forma individual.
Las APIs pueden permitir (y suelen permitir) el acceso aleatorio o el acceso al comienzo de un archivo sin inspeccionar el final. Esto es intencional, ya que es posible desencriptar \(M_i\) desde \(C_i\)sin desencriptar todos los bloques de texto cifrado anteriores y restantes.
Sin embargo, las APIs deben tener cuidado de no permitir que los usuarios confundan los errores de fin de archivo y de desencriptación: en ambos casos, es probable que la API tenga que mostrar un error, y si se ignora la diferencia, un adversario puede truncar archivos de manera eficaz.
Serialización y análisis de claves
Para serializar una clave en el formato “Tink Proto”, primero asignamos los parámetros de la manera más obvia al proto que se proporciona en aes_ctr_hmac_streaming.proto.
El campo version
debe establecerse en 0.
Luego, lo serializamos con la serialización de proto normal y, luego, incorporamos la cadena resultante en el valor del campo de un proto KeyData. Configuramos el campo type_url
como type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey
.
Luego, configuramos key_material_type
en SYMMETRIC
y lo incorporamos en un conjunto de claves. Por lo general, configuramos output_prefix_type
en RAW
. La excepción es que, si la clave se analizó con un valor diferente establecido para output_prefix_type
, Tink puede escribir RAW
o el valor anterior.
Para analizar una clave, invertimos el proceso anterior (de la manera habitual cuando se analizan protos). Se ignora el campo key_material_type
. Se puede ignorar el valor de output_prefix_type
o se pueden rechazar las claves que tengan un output_prefix_type
diferente de RAW
.
Se rechazarán las claves que tengan un version
diferente de 0.
Referencias
-
[HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. La encriptación autenticada en línea y su resistencia al uso inadecuado del nonce CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
[HS20] Seguridad de la encriptación de transmisión en la biblioteca de Tink de Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 ↩
-
[HKDF] Función de derivación de claves (HKDF) de extracción y expansión basada en HMAC, RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩