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
-
[HRRV15] Hoang, Reyhanitabar, Rogaway, Vizar. Online authentifizierte Verschlüsselung und Schutz vor Missbrauch durch Nonce-Wiederverwendung. CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
[HS20] Sicherheit der Streamingverschlüsselung in der Tink Library von Google. Hoang, Shen, 2020. https://eprint.iacr.org/2020/1019 ↩
-
[HKDF] HMAC-basierte Extract-and-Expand Key Derivation Function (HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 ↩