AEAD en flux continu HMAC AES-CTR

Ce document définit formellement la fonction mathématique représentée par les clés de streaming HMAC AES-CTR (encodées au format proto sous la forme type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey).

Ce chiffrement est basé sur [HRRV15]1. Pour une analyse de la sécurité, consultez [HS20]2. Notez également que les tests multilingues de Tink comportent un test aes_ctr_hmac_streaming_key_test.py qui contient test_manually_created_test_vector avec une procédure détaillée pour obtenir un texte chiffré.

Clé et paramètres

Les clés sont décrites par les parties suivantes (toutes les tailles de ce document sont exprimées en octets):

  • \(\mathrm{InitialKeyMaterial}\), une chaîne d'octets: matériel de clé initial.
  • \(\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}\).

Les clés valides répondent également aux propriétés suivantes:

  • \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\).
  • Si \(\mathrm{HmacHashType} = \mathrm{SHA1}\) alors \(\mathrm{HmacTagSize} \in \{10, \ldots, 20\}\).
  • Si \(\mathrm{HmacHashType} = \mathrm{SHA256}\) alors \(\mathrm{HmacTagSize} \in \{10, \ldots, 32\}\).
  • Si \(\mathrm{HmacHashType} = \mathrm{SHA512}\) alors \(\mathrm{HmacTagSize} \in \{10, \ldots, 64\}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + \mathrm{HmacTagSize} + 8\) (cela équivaut à\(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) , comme expliqué plus loin).

Les clés qui ne satisfont à aucune de ces propriétés sont rejetées par Tink (lorsque la clé est analysée ou lorsque la primitive correspondante est créée).

Fonction de chiffrement

Pour chiffrer un message \(\mathrm{Msg}\) avec des données associées\(\mathrm{AssociatedData}\), nous créons un en-tête, divisons le message en segments, chiffrons chaque segment et concaténons les segments. Nous expliquons ces étapes ci-dessous.

Créer l'en-tête

Pour créer l'en-tête, nous choisissons d'abord une chaîne \(\mathrm{Salt}\)aléatoire uniforme de longueur \(\mathrm{DerivedKeySize}\). Nous choisissons ensuite une chaîne\(\mathrm{NoncePrefix}\) aléatoire uniforme de longueur 7.

Nous définissons ensuite\(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\), où la longueur de l'en-tête est encodée en un seul octet. Nous constatons que\(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Ensuite, nous utilisons HKDF3 avec la fonction de hachage \(\mathrm{HkdfHashType}\)pour calculer le matériel de clé de longueur\(\mathrm{DerivedKeySize} + 32\) pour ce message :\(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). Les entrées sont utilisées dans les entrées respectives de\(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) est \(\mathrm{ikm}\),\(\mathrm{Salt}\) est le sel et\(\mathrm{AssociatedData}\) est utilisé comme \(\mathrm{info}\).

La chaîne \(k\) est ensuite divisée en deux parties \(k_1 \| k_2 := k\), de sorte que\(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) et \(\mathrm{len}(k_2) = 32\).

Divisez le message.

Le message \(M\) est ensuite divisé en parties: \(M = M_0 \| M_1 \| \cdots \| M_{n-1}\).

Leurs longueurs sont choisies de sorte qu'elles répondent aux critères suivants:

  • \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{HmacTagSize}\}\).
  • Si \(n > 1\), alors \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\).
  • Si \(n > 1\), \(M_{0}, \ldots, M_{n-2}\) doit avoir une longueur maximale conformément aux contraintes ci-dessus.

Dans cette division, \(n\) ne peut pas dépasser \(2^{32}\). Sinon, le chiffrement échoue.

Chiffrer les blocs

Pour chiffrer le segment \(M_i\), nous calculons d'abord\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\), où nous encodons \(\mathrm{i}\) en 4 octets à l'aide de l'encodage big-endian, et définissons l'octet $b$ sur 0x00 si $i < n-1$ et sur 0x01 dans le cas contraire.

Nous chiffrons ensuite \(M_i\) à l'aide de la clé AES CTR \(k_1\)et du vecteur d'initialisation\(\mathrm{IV}_i\). En d'autres termes, les entrées des invocations d'AES sont\(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\), où \(\mathrm{IV}_i\) est interprété comme un entier big-endian. Cela donne \(C'_i\).

Nous calculons la balise à l'aide de HMAC avec la fonction de hachage donnée par \(\mathrm{HmacHashType}\) et avec la clé \(k_2\) sur la concaténation\(\mathrm{IV}_i \| C'_i\).

Nous concaténons ensuite le texte chiffré suivi de la balise pour obtenir \(C_i\).

Concatenate les segments

Enfin, tous les segments sont concatenatés en tant que\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), qui est le texte chiffré final.

Fonction de déchiffrement

Le déchiffrement inverse simplement le chiffrement. Nous utilisons l'en-tête pour obtenir le nonce et déchiffrer chaque segment de texte chiffré individuellement.

Les API peuvent (et le font généralement) autoriser un accès aléatoire ou un accès au début d'un fichier sans inspecter la fin du fichier. Il s'agit d'une intention délibérée, car il est possible de déchiffrer \(M_i\) à partir de \(C_i\), sans déchiffrer tous les blocs de texte chiffré précédents et restants.

Toutefois, les API doivent veiller à ne pas permettre aux utilisateurs de confondre les erreurs de fin de fichier et de déchiffrement: dans les deux cas, l'API doit probablement renvoyer une erreur, et ignorer la différence peut permettre à un pirate informatique de tronquer efficacement des fichiers.

Sérialisation et analyse des clés

Pour sérialiser une clé au format "Tink Proto", nous mappons d'abord les paramètres de manière évidente dans le proto donné dans aes_ctr_hmac_streaming.proto. Le champ version doit être défini sur 0. Nous sérialisons ensuite cette valeur à l'aide de la sérialisation de proto normale, puis nous intégrons la chaîne obtenue dans la valeur du champ d'un proto KeyData. Nous définissons le champ type_url sur type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey. Nous définissons ensuite key_material_type sur SYMMETRIC et l'intégrons dans un ensemble de clés. Nous définissons généralement output_prefix_type sur RAW. L'exception est que si la clé a été analysée avec une valeur différente définie pour output_prefix_type, Tink peut écrire RAW ou la valeur précédente.

Pour analyser une clé, nous inversons le processus ci-dessus (comme d'habitude lors de l'analyse des protocoles). Le champ key_material_type est ignoré. La valeur de output_prefix_type peut être ignorée, ou les clés dont output_prefix_type est différent de RAW peuvent être rejetées. Les clés dont la valeur version est différente de 0 sont rejetées.

Références


  1. [HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Le chiffrement authentifié en ligne et sa résistance à l'utilisation abusive des nonces. CRYPTO 2015. https://eprint.iacr.org/2015/189 

  2. [HS20] Sécurité du chiffrement en streaming dans la bibliothèque Tink de Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 

  3. [HKDF] Fonction de dérivation de clé (HKDF, Extract-and-Expand Key Derivation Function) basée sur le HMAC, RFC 5869. https://www.rfc-editor.org/rfc/rfc5869