AES-CTR-HMAC-Streaming-AEAD

In diesem Dokument wird die mathematische Funktion formal definiert, die durch AES-CTR-HMAC-Streamingschlüssel dargestellt wird (im Proto-Format als type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey codiert).

Diese Verschlüsselung basiert lose auf [HRRV15]1. Eine Analyse der Sicherheit finden Sie in [HS20]2. Beachten Sie auch, dass die mehrsprachigen Tink-Tests einen Test aes_ctr_hmac_streaming_key_test.py enthalten, der test_manually_created_test_vector mit einer vollständigen Anleitung zum Abrufen eines Geheimtexts enthält.

Schlüssel und Parameter

Schlüssel werden durch die folgenden Teile beschrieben (alle Größen in diesem Dokument sind in Byte angegeben):

  • \(\mathrm{InitialKeyMaterial}\), ein Byte-String: das ursprüngliche Schlüsselmaterial.
  • \(\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}\).

Gültige Schlüssel erfüllen zusätzlich die folgenden Eigenschaften:

  • \(\mathrm{len}(\mathrm{InitialKeyMaterial}) \geq \mathrm{DerivedKeySize}\).
  • Wenn \(\mathrm{HmacHashType} = \mathrm{SHA1}\) , dann \(\mathrm{HmacTagSize} \in \{10, \ldots, 20\}\).
  • Wenn \(\mathrm{HmacHashType} = \mathrm{SHA256}\) , dann \(\mathrm{HmacTagSize} \in \{10, \ldots, 32\}\).
  • Wenn \(\mathrm{HmacHashType} = \mathrm{SHA512}\) , dann \(\mathrm{HmacTagSize} \in \{10, \ldots, 64\}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + \mathrm{HmacTagSize} + 8\) (wie später erläutert, entspricht dies\(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) ).

Schlüssel, die keine dieser Eigenschaften erfüllen, werden von Tink abgelehnt, entweder beim Parsen des Schlüssels oder beim Erstellen des entsprechenden Primitives.

Verschlüsselungsfunktion

Um eine Nachricht \(\mathrm{Msg}\) mit zugehörigen Daten\(\mathrm{AssociatedData}\)zu verschlüsseln, erstellen wir eine Kopfzeile, teilen die Nachricht in Segmente auf, verschlüsseln jedes Segment und verknüpfen die Segmente. Diese Schritte werden im Folgenden erläutert.

Kopfzeile erstellen

Um die Kopfzeile zu erstellen, wählen wir zuerst einen zufälligen String \(\mathrm{Salt}\)mit der Länge \(\mathrm{DerivedKeySize}\)aus. Als Nächstes wählen wir einen zufälligen String\(\mathrm{NoncePrefix}\) mit einer Länge von 7 aus.

Wir legen dann\(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\)fest, wobei die Länge des Headers als einzelnes Byte codiert wird. Wir haben festgestellt, dass\(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\).

Als Nächstes verwenden wir HKDF3 mit der Hash-Funktion \(\mathrm{HkdfHashType}\), um Schlüsselmaterial mit der Länge\(\mathrm{DerivedKeySize} + 32\) für diese Nachricht zu berechnen: \(k := \mathrm{HKDF}(\mathrm{InitialKeyMaterial}, \mathrm{Salt}, \mathrm{AssociatedData})\). Die Eingaben werden in den entsprechenden Eingaben von\(\mathrm{HKDF}\)verwendet: \(\mathrm{InitialKeyMaterial}\) ist \(\mathrm{ikm}\),\(\mathrm{Salt}\) ist das Salz und\(\mathrm{AssociatedData}\) wird als \(\mathrm{info}\)verwendet.

Der String \(k\) wird dann in zwei Teile \(k_1 \| k_2 := k\)aufgeteilt, sodass\(\mathrm{len}(k_1) = \mathrm{DerivedKeySize}\) und \(\mathrm{len}(k_2) = 32\).

Nachricht aufteilen

Die Nachricht \(M\) wird dann in Teile unterteilt: \(M = M_0 \| M_1 \| \cdots \| M_{n-1}\).

Ihre Längen werden so gewählt, dass sie folgende Anforderungen erfüllen:

  • \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{HmacTagSize}\}\).
  • Wenn \(n > 1\), dann \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{HmacTagSize}\}\).
  • Wenn \(n > 1\), muss \(M_{0}, \ldots, M_{n-2}\) gemäß den oben genannten Einschränkungen eine maximale Länge haben.

Bei dieser Aufteilung darf \(n\) höchstens \(2^{32}\)betragen. Andernfalls schlägt die Verschlüsselung fehl.

Blöcke verschlüsseln

Um das Segment \(M_i\)zu verschlüsseln, berechnen wir zuerst\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b \| 0x00000000\), wobei wir \(\mathrm{i}\) in 4 Byte mit Big-Endian-Codierung codieren und das Byte $b auf 0x00 setzen, wenn $i < n-1 ist, und auf 0x01 andernfalls.

Wir verschlüsseln dann \(M_i\) mit dem AES-CTR-Schlüssel \(k_1\)und dem Initialisierungsvektor\(\mathrm{IV}_i\). Mit anderen Worten: Die Eingaben für die Aufrufe von AES sind\(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\), wobei \(\mathrm{IV}_i\) als Big-Endian-Ganzzahl interpretiert wird. Das ergibt \(C'_i\).

Wir berechnen das Tag mit HMAC mit der Hash-Funktion \(\mathrm{HmacHashType}\) und dem Schlüssel \(k_2\) über die Konkatenierung\(\mathrm{IV}_i \| C'_i\).

Anschließend verketten wir den Geheimtext mit dem Tag, um \(C_i\)zu erhalten.

Segmente zusammenführen

Schließlich werden alle Segmente als\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\)zusammengefügt, was der endgültige Geheimtext ist.

Entschlüsselungsfunktion

Bei der Entschlüsselung wird die Verschlüsselung einfach umgekehrt. Wir verwenden den Header, um den Nonce zu erhalten, und entschlüsseln jedes Segment des Geheimtexts einzeln.

APIs können (und tun dies in der Regel) den Zufallszugriff oder den Zugriff auf den Anfang einer Datei zulassen, ohne das Ende der Datei zu prüfen. Das ist beabsichtigt, da es möglich ist, \(M_i\) von \(C_i\)zu entschlüsseln, ohne alle vorherigen und verbleibenden Geheimtextblöcke zu entschlüsseln.

Bei APIs sollte jedoch darauf geachtet werden, dass Nutzer das Ende der Datei nicht mit einem Entschlüsselungsfehler verwechseln: In beiden Fällen muss die API wahrscheinlich einen Fehler zurückgeben. Wenn der Unterschied ignoriert wird, kann ein Angreifer Dateien effektiv kürzen.

Serialisierung und Parsen von Schlüsseln

Um einen Schlüssel im Tink Proto-Format zu serialisieren, ordnen wir die Parameter zuerst auf die offensichtliche Weise dem Proto zu, das unter aes_ctr_hmac_streaming.proto angegeben ist. Das Feld version muss auf 0 gesetzt sein. Anschließend wird dies mit der normalen Proto-Serialisierung serialisiert und der resultierende String in das Wertfeld eines KeyData-Protos eingebettet. Wir setzen das Feld type_url auf type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey. Wir legen dann key_material_type auf SYMMETRIC fest und betten dies in ein Keyset ein. Wir legen den Wert output_prefix_type normalerweise auf RAW fest. Eine Ausnahme gilt, wenn der Schlüssel mit einem anderen Wert für output_prefix_type geparst wurde. In diesem Fall schreibt Tink entweder RAW oder den vorherigen Wert.

Um einen Schlüssel zu parsen, wenden wir den obigen Vorgang auf umgekehrte Weise an (wie beim Parsen von Protokollen üblich). Das Feld key_material_type wird ignoriert. Der Wert von output_prefix_type kann entweder ignoriert oder Schlüssel, deren output_prefix_type sich von RAW unterscheidet, abgelehnt werden. Schlüssel mit einem anderen Wert als 0 werden abgelehnt.version

Verweise


  1. [HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Online-authentifizierte Verschlüsselung und die Widerstandsfähigkeit gegen Missbrauch durch Wiederverwendung von Nonces. CRYPTO 2015. https://eprint.iacr.org/2015/189 

  2. [HS20] Security of Streaming Encryption in Google's Tink Library. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 

  3. [HKDF] HMAC-basierte Schlüsselableitungsfunktion (Extract-and-Expand Key Derivation Function, HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869