Freigeben über


Kontextheader in ASP.NET Core

Hintergrund und Theorie

Im Datenschutzsystem bezeichnet ein „Schlüssel“ ein Objekt, das authentifizierte Verschlüsselungsdienste bereitstellen kann. Jeder Schlüssel wird durch eine eindeutigen Bezeichner (Globally Unique Identifier, GUID) identifiziert und enthält algorithmische Informationen und entropisches Material. Es ist vorgesehen, dass jeder Schlüssel eine eindeutige Entropie aufweist, das System kann dies aber nicht erzwingen. Außerdem gilt es auch Entwickler zu berücksichtigen, die den Schlüsselring manuell ändern können, indem sie die algorithmischen Informationen eines vorhandenen Schlüssels im Schlüsselring ändern. Um unsere Sicherheitsanforderungen in solchen Fällen zu erfüllen, verfügt das Datenschutzsystem über ein Konzept der kryptografischen Agilität, das die sichere Verwendung eines einzelnen entropischen Werts über mehrere kryptografische Algorithmen hinweg ermöglicht.

Die meisten Systeme, die kryptografische Agilität unterstützen, tun dies, indem sie einige identifizierende Informationen über den Algorithmus in die Nutzdaten einfügen. Die OID des Algorithmus ist dafür in der Regel ein guter Kandidat. Ein Problem, auf das wir trafen, besteht jedoch darin, dass es mehrere Möglichkeiten gibt, denselben Algorithmus anzugeben: „AES“ (CNG) und die verwalteten Klassen Aes, AesManaged, AesCryptoServiceProvider, AesCng und RijndaelManaged (bestimmte Parameter) sind alle identisch, und wir müssen für eine Zuordnung all dieser Klassen zur richtigen OID sorgen. Wenn ein Entwickler einen benutzerdefinierten Algorithmus (oder sogar eine andere Implementierung von AES) bereitstellen möchte, muss er uns seine OID mitteilen. Dieser zusätzliche Registrierungsschritt macht die Systemkonfiguration besonders aufwendig.

Wir kamen daher zu dem Schluss, dass wir das Problem falsch angegangen waren. Eine OID sagt zwar, was der Algorithmus ist, diese Information ist uns aber eigentlich egal. Wenn wir einen einzelnen entropischen Wert sicher in zwei verschiedenen Algorithmen verwenden wollen, müssen wir nicht wissen, was die Algorithmen tatsächlich sind. Was uns eigentlich interessiert, ist, wie sie sich verhalten. Jeder ordentliche symmetrische Blockchiffre-Algorithmus ist auch eine starke pseudozufällige Permutation (Pseudorandom Permutation, PRP): Legen Sie die Eingaben (Schlüssel, Verkettungsmodus, IV, Klartext), und den Chiffretext fest, wird sich die Ausgabe mit überwältigender Wahrscheinlichkeit von jedem anderen symmetrischen Blockverschlüsselungsalgorithmus mit gleichen Eingaben unterscheiden. Ebenso ist jede ordentliche schlüsselgebundene Hashfunktion auch eine starke pseudozufällige Funktion (Pseudorandom Function, PRF), und bei einer Reihe festgelegter Eingaben unterscheidet sich ihre Ausgabe mit größter Wahrscheinlichkeit von jeder anderen schlüsselgebundenen Hashfunktion.

Wir verwenden dieses Konzept starker PRPs und PRFs, um einen Kontextheader zu erstellen. Dieser Kontextheader fungiert im Wesentlichen als stabiler Fingerabdruck der Algorithmen, die für einen bestimmten Vorgang verwendet werden, und bietet die kryptografische Agilität, die das Datenschutzsystem benötigt. Dieser Header ist reproduzierbar und wird später als Teil des Ableitungsprozesses des Unterschlüssels verwendet. Es gibt zwei verschiedene Möglichkeiten, den Kontextheader abhängig von den Betriebsmodi der zugrunde liegenden Algorithmen zu erstellen.

Verschlüsselung im CBC-Modus und HMAC-Authentifizierung

Der Kontextheader besteht aus den folgenden Komponenten:

  • [16 Bits] Der Wert „00 00“, der ein Marker mit der Bedeutung „CBC-Verschlüsselung und HMAC-Authentifizierung“ ist.

  • [32 Bits] Die Schlüssellänge (in Bytes, Big-Endian) des symmetrischen Blockverschlüsselungsalgorithmus.

  • [32 Bits] Die Blockgröße (in Bytes, Big-Endian) des symmetrischen Blockverschlüsselungsalgorithmus.

  • [32 Bits] Die Schlüssellänge (in Bytes, Big-Endian) des HMAC-Algorithmus. (Derzeit entspricht die Schlüsselgröße immer der Digestgröße.)

  • [32 Bits] Die Digestgröße (in Bytes, Big-Endian) des HMAC-Algorithmus.

  • EncCBC(K_E, IV, ""), die Ausgabe des symmetrischen Blockverschlüsselungsalgorithmus bei einer leeren Zeichenfolgeneingabe und bei der IV ein Nullvektor. Die Konstruktion von K_E wird weiter unten beschrieben.

  • MAC(K_H, ""), die Ausgabe des HMAC-Algorithmus bei der die Zeichenfolgeneingabe leer ist. Die Konstruktion von K_H wird weiter unten beschrieben.

Im Idealfall könnten Nullvektoren für K_E und K_Hübergeben. Wir möchten jedoch vermeiden, dass der zugrunde liegende Algorithmus vor dem Ausführen von Vorgängen (insbesondere DES und 3DES) auf das Vorhandensein von schwachen Schlüsseln überprüft, was die Verwendung eines einfachen oder wiederholbaren Musters wie einem Nullvektor ausschließt.

Stattdessen verwenden wir NIST SP800-108 KDF im Zählermodus (siehe NIST SP800-108 im Abschnitt 5.1) mit einem Schlüssel der Länge Null, einer Bezeichnung, einem Kontext und HMACSHA512 als zugrunde liegender PRF. Wir leiten | K_E | + | K_H | Bytes der Ausgabe ab und zerlegen dann das Ergebnis in K_E und K_H. Mathematisch lässt sich dies folgendermaßen darstellen.

( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "")

Beispiel: AES-192-CBC und HMACSHA256

Betrachten Sie beispielsweise den Fall, in dem der symmetrische Blockverschlüsselungsalgorithmus AES-192-CBC und der Validierungsalgorithmus HMACSHA256 ist. Das System würde dann den Kontextheader mithilfe der folgenden Schritte generieren.

Lassen Sie zuerst ( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "") sein, wobei | K_E | = 192 bits und | K_H | = 256 bits den angegebenen Algorithmen entsprechen. Dies führt im folgenden Beispiel zu K_E = 5BB6..21DD und K_H = A04A..00A9:

5B B6 C9 83 13 78 22 1D 8E 10 73 CA CF 65 8E B0
61 62 42 71 CB 83 21 DD A0 4A 05 00 5B AB C0 A2
49 6F A5 61 E3 E2 49 87 AA 63 55 CD 74 0A DA C4
B7 92 3D BF 59 90 00 A9

Berechnen Sie als Nächstes Enc_CBC (K_E, IV, "") für AES-192-CBC bei wie bei gegebenem IV = 0* und K_E (siehe oben).

result := F474B1872B3B53E4721DE19C0841DB6F

Berechnen Sie dann MAC(K_H, "") für HMACSHA256 bei gegebenem K_H (siehe oben).

result := D4791184B996092EE1202F36E8608FA8FBD98ABDFF5402F264B1D7211536220C

Dies erzeugt den folgenden vollständigen Kontextheader:

00 00 00 00 00 18 00 00 00 10 00 00 00 20 00 00
00 20 F4 74 B1 87 2B 3B 53 E4 72 1D E1 9C 08 41
DB 6F D4 79 11 84 B9 96 09 2E E1 20 2F 36 E8 60
8F A8 FB D9 8A BD FF 54 02 F2 64 B1 D7 21 15 36
22 0C

Dieser Kontextheader ist der Fingerabdruck des authentifizierten Verschlüsselungsalgorithmuspaars (AES-192-CBC-Verschlüsselung und HMACSHA256-Validierung). Die Komponenten sind wie oben beschrieben:

  • den Marker (00 00)

  • die Länge des Blockverschlüsselungsschlüssels (00 00 00 18)

  • die Größe des Blockverschlüsselungsblocks (00 00 00 10)

  • die Länge des HMAC-Schlüssels (00 00 00 20)

  • die HMAC-Digestgröße (00 00 00 20)

  • die PRP-Ausgabe (F4 74 - DB 6F) der Blockchiffre und

  • die HMAC PRF-Ausgabe (D4 79 - end).

Hinweis

Die Kombination aus CBC-Verschlüsselungsmodus und HMAC-Authentifizierungskontextheader wird auf die gleiche Weise erstellt, unabhängig davon, ob die Implementierungen der Algorithmen von Windows CNG oder von verwalteten SymmetricAlgorithm- und KeyedHashAlgorithm-Typen bereitgestellt werden. Dadurch können Anwendungen, die auf verschiedenen Betriebssystemen ausgeführt werden, zuverlässig denselben Kontextheader erzeugen, auch wenn sich die Implementierungen der Algorithmen zwischen Betriebssystemen unterscheiden. (In der Praxis muss keyedHashAlgorithm kein ordnungsgemäßer HMAC sein. Es kann sich um einen beliebigen schlüsselgebundenen Hashalgorithmustyp handeln.)

Beispiel: 3DES-192-CBC und HMACSHA1

Lassen Sie zuerst ( K_E || K_H ) = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "") sein, wobei | K_E | = 192 bits und | K_H | = 160 bits den angegebenen Algorithmen entsprechen. Dies führt im folgenden Beispiel zu K_E = A219..E2BB und K_H = DC4A..B464:

A2 19 60 2F 83 A9 13 EA B0 61 3A 39 B8 A6 7E 22
61 D9 F8 6C 10 51 E2 BB DC 4A 00 D7 03 A2 48 3E
D1 F7 5A 34 EB 28 3E D7 D4 67 B4 64

Berechnen Sie als Nächstes Enc_CBC (K_E, IV, "") für 3DES-192-CBC bei gegebenem IV = 0* und K_E (siehe oben).

result := ABB100F81E53E10E

Berechnen Sie dann MAC(K_H, "") für HMACSHA1 bei gegebenem K_H (siehe oben).

result := 76EB189B35CF03461DDF877CD9F4B1B4D63A7555

Dadurch wird der vollständige Kontextheader erzeugt, bei dem es sich um einen Fingerabdruck des authentifizierten Verschlüsselungsalgorithmuspaars (3DES-192-CBC-Verschlüsselung und HMACSHA1-Validierung) handelt, wie unten gezeigt:

00 00 00 00 00 18 00 00 00 08 00 00 00 14 00 00
00 14 AB B1 00 F8 1E 53 E1 0E 76 EB 18 9B 35 CF
03 46 1D DF 87 7C D9 F4 B1 B4 D6 3A 75 55

Er hat folgenden Komponenten:

  • den Marker (00 00)

  • die Länge des Blockverschlüsselungsschlüssels (00 00 00 18)

  • die Größe des Blockverschlüsselungsblocks (00 00 00 08)

  • die Länge des HMAC-Schlüssels (00 00 00 14)

  • die HMAC-Digestgröße (00 00 00 14)

  • die PRP-Ausgabe (AB B1 - E1 0E) der Blockchiffre und

  • die HMAC PRF-Ausgabe (76 EB - end).

Galois/Counter Mode-Verschlüsselung und -Authentifizierung

Der Kontextheader besteht aus den folgenden Komponenten:

  • [16 Bits] Der Wert „00 01“, der ein Marker mit der Bedeutung „GCM-Verschlüsselung und -Authentifizierung“ ist.

  • [32 Bits] Die Schlüssellänge (in Bytes, Big-Endian) des symmetrischen Blockverschlüsselungsalgorithmus.

  • [32 Bits] Die Nonce-Größe (in Bytes, Big-Endian), die bei authentifizierten Verschlüsselungsvorgängen verwendet wird. (Für unser System ist dies auf die Nonce-Größe von 96 Bits festgelegt.)

  • [32 Bits] Die Blockgröße (in Bytes, Big-Endian) des symmetrischen Blockverschlüsselungsalgorithmus. (Für GCM ist dies auf eine Blockgröße von 128 Bits festgelegt.)

  • [32 Bits] Die Größe des Authentifizierungstags (in Bytes, Big Endian), die von der authentifizierten Verschlüsselungsfunktion erzeugt wird. (Für unser System ist dies auf die Taggröße von 128 Bits festgelegt.)

  • [128 Bits] Der Tag von Enc_GCM (K_E, nonce, ""), die Ausgabe des symmetrischen Blockverschlüsselungsalgorithmus, bei der die Zeichenfolgeneingabe leer und die Nonce ein 96-Bit-Nullvektor ist.

K_E wird mit demselben Mechanismus wie im Szenario „CBC-Verschlüsselung und HMAC-Authentifizierung“ abgeleitet. Da hier jedoch kein K_H im Spiel ist, haben wir im Wesentlichen | K_H | = 0, und der Algorithmus reduziert sich auf die folgende Form.

K_E = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "")

Beispiel: AES-256-GCM

Lassen Sie zuerst K_E = SP800_108_CTR(prf = HMACSHA512, key = "", label = "", context = "") mit | K_E | = 256 bits sein.

K_E := 22BC6F1B171C08C4AE2F27444AF8FC8B3087A90006CAEA91FDCFB47C1B8733B8

Berechnen Sie als Nächstes das Authentifizierungstag von Enc_GCM (K_E, nonce, "") für AES-256-GCM bei gegebenem nonce = 096 und K_E (siehe oben).

result := E7DCCE66DF855A323A6BB7BD7A59BE45

Dies erzeugt den folgenden vollständigen Kontextheader:

00 01 00 00 00 20 00 00 00 0C 00 00 00 10 00 00
00 10 E7 DC CE 66 DF 85 5A 32 3A 6B B7 BD 7A 59
BE 45

Er hat folgenden Komponenten:

  • den Marker (00 01)

  • die Länge des Blockverschlüsselungsschlüssels (00 00 00 20)

  • die Noncegröße (00 00 00 0C)

  • die Größe des Blockverschlüsselungsblocks (00 00 00 10)

  • die Größe (00 00 00 10) des Authentifizierungstags und

  • das Authentifizierungstag aus der Ausführung der Blockchiffre (E7 DC - end).