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
-
[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 ↩
-
[HS20] Security of Streaming Encryption in Google's Tink Library. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 ↩
-
[HKDF] HMAC-basierte Schlüsselableitungsfunktion (Extract-and-Expand Key Derivation Function, HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩