AES-CTR-HMAC-Streaming-AEAD

In diesem Dokument wird die mathematische Funktion 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 grob auf [HRRV15]1. Für eine Analyse der Sicherheit verweisen wir auf [HS20]2. Die sprachübergreifenden Tink-Tests haben außerdem einen Test aes_ctr_hmac_streaming_key_test.py, der test_manually_created_test_vector mit einer vollständigen Schritt-für-Schritt-Anleitung zum Abrufen von Geheimtext enthält.

Schlüssel und Parameter

Schlüssel werden in folgenden Teilen beschrieben (alle Größen in diesem Dokument werden in Byte angegeben):

  • \(\mathrm{InitialKeyMaterial}\), ein Bytestring: 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 Attribute:

  • \(\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\) (Dies ist wie später erläutert\(\mathrm{len}(\mathrm{Header}) + \mathrm{HmacTagSize}\) ).

Schlüssel, die keines dieser Eigenschaften erfüllen, werden von Tink abgelehnt (entweder beim Parsen des Schlüssels oder beim Erstellen der entsprechenden Primitive).

Verschlüsselungsfunktion

Um eine Nachricht \(\mathrm{Msg}\) mit den zugehörigen Daten\(\mathrm{AssociatedData}\)zu verschlüsseln, erstellen wir einen Header, teilen die Nachricht in Segmente auf, verschlüsseln die einzelnen Segmente und verketten die Segmente. Diese Schritte werden im Folgenden erläutert.

Header erstellen

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

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

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

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

Nachricht aufteilen

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

Ihre Länge wird so gewählt, dass sie folgende Anforderungen erfüllt:

  • \(\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\), dann muss \(M_{0}, \ldots, M_{n-2}\) gemäß den oben genannten Einschränkungen für die Einschränkungen eine maximale Länge haben.

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

Blöcke verschlüsseln

Zum Verschlüsseln des Segments \(M_i\)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$ und andernfalls 0x01.

Anschließend wird \(M_i\) mit dem AES-CTR-Schlüssel \(k_1\)und dem Initialisierungsvektor\(\mathrm{IV}_i\)verschlüsselt. Mit anderen Worten, die Eingaben für die AES-Aufrufe sind\(\mathrm{IV}_i, \mathrm{IV}_i + 1, \mathrm{IV}_i + 2, \ldots\), wobei \(\mathrm{IV}_i\) als Big-Endian-Ganzzahl interpretiert wird. So erhalten Sie \(C'_i\).

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

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

Segmente verketten

Schließlich werden alle Segmente zu\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\)verkettet. Dies ist der endgültige Geheimtext.

Entschlüsselungsfunktion

Bei der Entschlüsselung wird die Verschlüsselung einfach invertiert. Wir verwenden den Header, um die Nonce abzurufen, und entschlüsseln jedes Geheimtextsegment einzeln.

APIs ermöglichen (und in der Regel) den zufälligen Zugriff oder den Zugriff auf den Anfang einer Datei, ohne das Ende der Datei zu prüfen. Dies ist beabsichtigt, da es möglich ist, \(M_i\) aus \(C_i\)zu entschlüsseln, ohne alle vorherigen und verbleibenden Geheimtextblöcke zu entschlüsseln.

Bei APIs sollte jedoch darauf geachtet werden, dass Nutzer keine Dateiende- und Entschlüsselungsfehler verwechseln: In beiden Fällen muss die API wahrscheinlich einen Fehler zurückgeben. Wenn Sie den Unterschied ignorieren, kann ein Angreifer Dateien effektiv kürzen.

Serialisierung und Parsen von Schlüsseln

Zum Serialisieren eines Schlüssels im "Tink Proto"-Format 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 mithilfe der normalen Proto-Serialisierung serialisiert und der resultierende String in den Feldwert eines KeyData-Proto eingebettet. Wir setzen das Feld type_url auf type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey. Anschließend setzen wir key_material_type auf SYMMETRIC und betten dies in ein Keyset ein. Normalerweise setzen wir output_prefix_type auf RAW. Die Ausnahme besteht darin, dass Tink entweder RAW oder den vorherigen Wert schreibt, wenn der Schlüssel mit einem anderen Wert für output_prefix_type geparst wurde.

Um einen Schlüssel zu parsen, kehren wir den obigen Prozess um (wie beim Parsen von Proto-Dateien üblich). Das Feld key_material_type wird ignoriert. Der Wert von output_prefix_type kann entweder ignoriert werden oder Schlüssel, deren output_prefix_type sich von RAW unterscheidet, können abgelehnt werden. Schlüssel mit einer anderen version als 0 werden abgelehnt.

Verweise


  1. [HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Online authentifizierte Verschlüsselung und Schutz vor Missbrauch durch Nonce-Wiederverwendung. CRYPTO 2015. https://eprint.iacr.org/2015/189 

  2. [HS20] Sicherheit der Streamingverschlüsselung in der Tink Library von Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 

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