Verschlüsselung mit .NET

Mein erster sinnvoller Post beschäftigt sich mit den Verschlüsselungsmechanismen die .NET anbietet und beziehe dabei einen Großteil meiner Informationen aus dem Self-Paced Training Kit für die 70-536 Prüfung. Ich werde bei den Erläuterungen höchstens am Rande auf die dahinterliegende Mathematik eingehen, viel wichtiger erscheint mir die Klassenstruktur sowie deren Verwendung. Wer mehr über die Hintergründe wissen möchte ist mit Wikipedia oder mit einem echten Lehrbuch zum Thema weit besser bedient. Für die im Framework vorhandenen und nicht erläuterten Verfahren bemühe man bitte die MSDN.

Weiterhin ist zu sagen, dass hier nur die Standardimplementierungen behandelt werden. Für die Cryptography API Next Generation (CNG) die seit Windows Vista zur Verfügung steht wird es ein eigenes Blogpost geben.

Etwas Hintergrundwissen muss sein

Grundsätzlich unterscheiden wir zwei Arten von Verschlüsselungen: symmetrisch und asymmetrisch. Symmetrisch bezeichnet dabei jene Verschlüsselungsverfahren die nur einen Schlüssel nutzen mit denen die Daten ver- und entschlüsselt werden. Asymmetrische hingegen verwenden zwei verschiedene Schlüssel.

Grundsätzlich sind die asymmetrischen Verfahren sicherer, da man nur einen der beiden Schlüssel – den Public Key – veröffentlicht. Andererseits ist sie auch aufwendiger für große Datenmengen. Weshalb symmetrische und asymmetrische Verfahren gern gemischt verwendet werden um zum Beispiel den Schlüssel des symmetrischen Verfahrens asymmetrisch zu verschlüsseln (SSL).

Beide Verfahren resultieren in unterschiedlichen Basisklassen für die späteren Implementierungen der unterschiedlichen Algorithmen. Sie heißen, wer hätte es gedacht, SymmetricAlgorithm und AsymmetricAlgorithm.

Die symmetrischen Verfahren

Für jedes symmetrische Verfahren benötigt man einen Schlüssel (Key) und ein Initialisierungs Vektor (IV). Falls nicht angegeben werden sie automatisch generiert können aber auch manuell mit einer entsprechenden öffentlichen Methode erstellt werden.

Das Objekt welches den eigentlich Ent- bzw. Verschlüsselungsalgorithmus kapselt wird über eine Fabrikmethode des jeweiligen SymmetricAlgorithm Objekts erstellt, welche da heißen CreateEncryptor bzw. CreateDencryptor.

Der Clou an dieser Stelle ist, dass bisher keinerlei tatsächliche Arbeit von statten ging. Die tatsächliche Verschlüsselung geschieht in einem sogenannten CryptoStream dieser verhält sich wie jeder andere Stream auch und kann ebenso verwendet werden. Bei der Erstellung wird ihm einfach eine entsprechende Referenz mitgegeben, er wird mit dem Encrypter bzw. Decrypter versehen und danach wird angegeben wann die Umwandlung statt finden soll, beim Lesen oder Schreiben. Ich denke die Stäken die sich daraus ergeben sind offensichtlich (Schachtelung von Streamobjekten, verwenden von Streamreadern und -writern, …).

SymmetricAlgorithm cryptoServiceProvider = new GewünschterAlgortihmus();
ICryptoTransform cryptoTransform = cryptoServiceProvider.CreateEncryptor();
CryptoStream stream = new CryptoStream(inputStream,cryptoTransform,CryptoStreamMode.Read);
StreamReader reader = new StreamReader(stream);

Umgang mit Schlüsselinformationen

Gut, wir haben gelernt, dass man mit einem Schlüssel etwas verschlüsseln kann. Aber wie gehen wir jetzt mit Situationen um in denen Verschlüsselnder und Entschlüsselnder unterschiedlich sind und vorallem welche Daten müssen wir eigentlich austauschen bzw. speichern, damit die Daten auch wieder hergestellt werden können?

Ausgetauscht werden muss grundsätzlich:

  • Typ des Algorithmus
  • Initialisierungs Vektor (IV)
  • Der Schlüssel selbst

Mindestens zwei dieser Informationen sind sicherheitskritisch und müssten selbst auf verschlüsselte Art und Weise übertragen werden. Dies könnte man zum einen über ein asymmetrisches Verfahren tun oder man vereinbart ein Passwort aus welchem der entsprechende IV und Schlüssel generiert wird.

byte[] salt = new byte[] { 0xAA, 0xFA, 0x02, 0x30, 0x04, 0x64, 0x60 };
DESCryptoServiceProvider serviceProvider = new DESCryptoServiceProvider();

Rfc2898DeriveBytes generator = new Rfc2898DeriveBytes(password, salt);
// divided by 8 because properties give value as bit not as byte
serviceProvider.IV = generator.GetBytes(serviceProvider.BlockSize / 8);
serviceProvider.Key = generator.GetBytes(serviceProvider.KeySize / 8);

Diese Generierung kann über die Klasse Rfc2898DeriveBytes geschehen. Nicht zu verwechseln mit PasswordDeriveBytes der Implementierung aus .NET 1.1 Tagen die weniger sicher ist und bei deren Verwendung eine Warnung ausgegeben wird. Weiterhin ist auch hier auf die richtige Verwendung der Klasse zu achten, da es sonst zu erheblichen Geschwindkeitseinbußen kommen kann.

Warum soll man sich noch mal die Mühe machen? Ist der Salt und Algorithmus bei Empfänger und Sender gleich reicht es aus ein durch Menschen lesbares Passwort zu verwenden. Denn „P@WoRd“ merkt sich leichter als „DWhBCP9fzJEn7WIj0rDIag==

Die asymmetrischen Verfahren

Die asymmetrischen Verfahren sind leider weniger generisch gehalten. Dies mag darauf zurück zu führen sein, dass es sich um Wrapper handelt welche unmanaged Code kapseln bzw. das mit RSA tatsächlich nur ein asymmetrisches Verschlüsselungsverfahren zur Verfügung steht – DAS wird nur zum Signieren von Daten genutzt.

So bietet die Basisklasse nur Funktionen zum Exportieren und Importieren von Schlüsselinformationen, sowie zur Konfiguration eben jener. Die eigentlichen Funktionen zur Verschlüsselung werden in der abstrakten Klasse RSA definiert und im davon abgeleiteten RSACryptoServiceProvider realisiert.

Dieser bietet neben anderen Funktionen die für uns an dieser Stelle wichtigen Methoden Encrypt und Decrypt. Welche lapidar ausgedrückt aus einem normalen byte array ein verschlüsseltes machen. Hinter der cryptischen Bezeichnung des zweiten Parameters versteckt sich die Festlegung ob als Algorithmus OAEP (wenn true) oder PKCS (wenn false) verwendet werden sollen. Es versteht sich von selbst, dass diese bei Verschlüsselung und Entschlüsselung gleich zu wählen sind.

RSACryptoServiceProvider cryptoServiceProvider = new RSACryptoServiceProvider();
byte[] encrypt = cryptoServiceProvider.Encrypt(data, true);

Veröffentlichung von Schlüsselinformationen

Auch bei asymmetrischen Verfahren ist der Austausch von Schlüsseln wichtig. Muss man den öffentlichen Schlüssel doch zugänglich machen damit er zur Verschlüsselung benutzt werden kann und den dazu passende private irgendwo sicher verwahren um nicht das gesamte Konzept auszuhebeln.

Die einfachste Möglichkeit der Speicherung welcher man sich bedienen kann ist die ExportParameters Methode bzw. ihr importierender Pendant. Mit dieser wird ein Objekt erstellt das alle notwendigen Daten und wenn gewollt (Parameter auf true setzen) auch privaten Informationen enthält.

Für den privaten Schlüssel ist es jedoch sicherer die entsprechenden Schlüsselinformationen in einem dafür vorgesehen Container zu hinterlegen. Diese Container können auf Benutzer- oder Maschinenebene gespeichert werden. Auf ersterer kann nur der tatsächliche Nutzer auf die Schlüssel zugreifen, bei letzterer alle an einer Maschine angemeldeten, insofern sie die notwendigen Privilegien vom Administrator zugeteilt bekommen haben.

Um dies zu erreichen bedient man sich der CspParameters Klasse welcher man entsprechende Optionen über die Property Flags zuweisen kann und mit der man einen zu verwenden Container angeben kann.

CspParameters parameters = new CspParameters();
parameters.KeyContainerName = "KeyContainer";
parameters.Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore;

RSACryptoServiceProvider cryptoServiceProvider = new RSACryptoServiceProvider(parameters);
cryptoServiceProvider.PersistKeyInCsp = true;

Exportiert oder gelöscht werden können Schlüsselcontainer mit der Konsolenanwendung Aspnet_regiis.exe

Ein Wort zur Speicherung verschlüsselter Daten

Beim asymmetrischen Verfahren steht man vor einem Problem gegenüber dem symetrischen, man muss die Daten aufbereiten bevor sie verschlüsselt werden können, da die entsprechenden Methoden nur Bytearrays als Eingabewerte akzeptieren. Hierbei ist darauf zu achten, dass eben jene Aufbereitung die Daten ebenso verändern kann wie es die Verschlüsselung tut. Sollten Daten also nicht wieder entschlüsselt werden können muss man prüfen ob die Daten nicht evtl. irgendwo „zerkonvertiert“ wurden…