AEAD en flux continu HMAC AES-CTR

Ce document définit officiellement 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 s'appuie globalement sur [HRRV15]1. Pour une analyse de la sécurité, reportez-vous au [HS20]2. Notez également que les tests interlangages de Tink comportent un test aes_ctr_hmac_streaming_key_test.py qui contient test_manually_created_test_vector avec un tutoriel complet sur l'obtention d'un texte chiffré.

Clé et paramètres

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

  • \(\mathrm{InitialKeyMaterial}\), une chaîne d'octets: matériel initial de la clé.
  • \(\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 satisfont é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 refusées par Tink (soit lors de l'analyse de la clé, soit lors de la création de la primitive correspondante).

Fonction de chiffrement

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

Créer l'en-tête

Pour créer l'en-tête, nous commençons par choisir une chaîne aléatoire uniforme \(\mathrm{Salt}\) de longueur \(\mathrm{DerivedKeySize}\). Nous choisissons ensuite une chaîne aléatoire uniforme \(\mathrm{NoncePrefix}\) 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 tant qu'octet unique. Notez que\(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Nous utilisons ensuite 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 correspondantes de\(\mathrm{HKDF}\): \(\mathrm{InitialKeyMaterial}\) est \(\mathrm{ikm}\),\(\mathrm{Salt}\) est le salage et\(\mathrm{AssociatedData}\) est utilisé en tant que \(\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\).

Division du message...

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

Leur longueur est choisie de sorte qu'ils 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 la valeur est \(n > 1\), \(M_{0}, \ldots, M_{n-2}\) doit avoir une longueur maximale conformément aux contraintes ci-dessus.

Dans cette répartition, \(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 encoderons \(\mathrm{i}\) sur 4 octets à l'aide de l'encodage big-endian, et définissons l'octet $b$ sur 0x00 si $i < n-1$ et 0x01 dans le cas contraire.

Nous chiffrons ensuite \(M_i\) à l'aide de la clé CTR AES \(k_1\)et du vecteur d'initialisation\(\mathrm{IV}_i\). En d'autres termes, les entrées des appels 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 le tag à l'aide de HMAC avec la fonction de hachage fournie par \(\mathrm{HmacHashType}\) et avec la clé \(k_2\) par rapport à la concaténation\(\mathrm{IV}_i \| C'_i\).

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

Concaténer les segments

Enfin, tous les segments sont concaténés sous la forme\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\), le texte chiffré final.

Fonction de déchiffrement

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

Les API peuvent autoriser (ce qui est généralement le cas) un accès aléatoire ou un accès au début d'un fichier sans inspecter la fin de celui-ci. C'est intentionnel, 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 le fait d'ignorer la différence peut permettre à un adversaire de tronquer efficacement les fichiers.

Sérialisation et analyse des clés

Pour sérialiser une clé au format "Tink Proto", nous devons d'abord mapper les paramètres de façon évidente dans le proto fourni dans aes_ctr_hmac_streaming.proto. Le champ "version" doit être défini sur 0. Nous allons ensuite sérialiser ceci à l'aide de la sérialisation proto normale et intégrer la chaîne obtenue dans la valeur de champ d'un fichier proto KeyData. Nous avons défini le champ type_url sur type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey. Ensuite, nous définissons key_material_type sur SYMMETRIC et l'intégrons à une collection de clés. Nous définissons généralement output_prefix_type sur RAW. Exception : 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 inverserons le processus ci-dessus (comme d'habitude lors de l'analyse de fichiers proto). Le champ key_material_type est ignoré. La valeur de output_prefix_type peut être ignorée, ou les clés dont le output_prefix_type est différent de RAW peuvent être refusées. Les clés dont le version est différent de 0 sont refusées.

Références


  1. [HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Le chiffrement par authentification en ligne et sa résistance aux utilisations abusives de nonce-réutilisation. CRYPTO 2015. https://eprint.iacr.org/2015/189 

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

  3. [HKDF] Fonction de dérivation de clé d'extraction et de développement basée sur HMAC (HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869