Kryptographie mit C# – Teil 1

Verschlüsselung ist in aller Munde. Die Bevölkerung wird langsam für das Thema sensibilisiert. Die Geheimdienste wollen am Liebsten alles Speichern und Auswerten, die Politik traut sich noch nicht richtig an das Thema heran. Fest steht jedoch: Privatsphäre ist wichtig und muss geschützt werden. Du hast nichts zu verbergen? Warum verschickst du dann Briefe, obwohl Postkarten günstiger sind?

Auch mit C# ist Verschlüsselung möglich, jedoch wundert es mich, dass es nur schwer auffindbare Anleitungen zu dem Thema gibt. Zudem sind diese meist auf Englisch. Es wird also Zeit selbst Hand anzulegen.

Einführung

Vorweg: Auf eine detaillierte Beschreibung möchte ich verzichten. Im Internet gibt es genug Quellen, die die Thematik bis ins kleinste Detail herunterbrechen. Stattdessen belasse ich es bei den nötigsten Grundlagen, um diesen Artikel nachvollziehbar zu machen. Dennoch habe ich an den entsprechenden Stellen Links zu weiterführenden Informationen eingefügt. Auch möchte ich noch erwähnen, dass mir während des Schreibens auffiel, dass der Text für einen Artikel alleine viel zu lang geworden ist. Deshalb habe ich mich entschlossen ihn in zwei Teilen zu veröffentlichen.

Symmetrische Verschlüsselung
Bildquelle [1]

Beginnen wir mit der symmetrischen Verschlüsselung. Hierzu wird ein und dasselbe Kennwort verwendet, um damit Nachrichten zu ver- und entschlüsseln. Statt Kennwort spricht man im Fachjargon von Schlüssel.

Das Problem dabei ist, dass der Schlüsselaustausch nicht einfach ist, denn wie gebe ich ihn sicher weiter?. Auch die Frage der Authentizität muss geklärt werden: Wer ist für die verschlüsselte Nachricht verantwortlich? Alle, die den Schlüssel besitzen?
Dafür spricht der im Vergleich zur gleich folgenden Verschlüsselung geringe Rechenaufwand. Deshalb werden sie dort eingesetzt, wo häufige Ver- und Entschlüsselungen vorkommen. Hier hat sich der Advanced Encryption Standard (kurz AES) etabliert.

Asymmetrische Verschlüsselung
Bildquelle [1]

Bei der asymmetrischen Verschlüsselung gibt es einen privaten und einen aus ihm generierten öffentlichen Schlüssel. Der private muss unbedingt geheim gehalten, wobei der öffentliche möglichst bekannt sein sollte. Im Internet existieren dazu sogenannte „Schlüsselserver“, auf die man seinen öffentlichen Schlüssel hinterlegen darf, die dann jeder einsehen kann.

Bildquelle [1]

Möchte uns nun jemand eine verschlüsselte Nachricht schicken, benutzt der Versender zum Verschlüsseln der Nachricht unseren öffentlichen Schlüssel. Diese Nachricht können nur wir, die im Besitz des privaten Schlüssels sind, wieder entschlüsseln. Es ist nicht möglich, mit unserem öffentlichen Schlüssel die zuvor damit verschlüsselte Nachricht wieder zu entschlüsseln.

Bildquelle [1]

Bei der Frage der Authentizität einer Nachricht haben wir einen großen Vorteil. Nehmen wir an, wir möchten mit unserem privaten Schlüssel eine Nachricht verschlüsseln (in diesem Fall sprechen wir von „signieren“), die alle, die im Besitz des öffentlichen Schlüssels sind, wieder entschlüsseln können. Das Besondere ist, dass der Empfänger in der Lage ist, genau zu sagen, von wem die Nachricht stammt. Das ist dadurch möglich, dass jeder öffentliche Schlüssel nur zu einem geheimen Schlüssel passt – ein beliebiger anderer öffentlicher oder gar privater Schlüssel kann die Nachricht nicht entschlüsseln. Wenn der Empfänger also in der Lage ist die Nachricht zu entschlüsseln, kann er genau bestimmen, wer die Nachricht verfasst hat.

1977 haben die Kryptologen Rivest, Shamir und Adleman (kurz: RSA) einen Algorithmus veröffentlicht, der heute immer noch den Standard darstellt. Die Sicherheit ruht hier auf ausgewählte, möglichst große Primzahlen.
Asymmetrische Verschlüsselung benötigt einen höheren Rechenaufwand als symmetrische Algorithmen. Aus diesem Grund setzt man häufig auf hybride Verfahren:
Zunächst wird ein Schlüssel für das symmetrische Verfahren erstellt. Dieser wird mit einem asymmetrischen Algorithmus verschlüsselt und an den Kommunikationspartner geschickt. Die weitere Kommunikation findet dann über das schnelle symmetrische Verfahren statt. Browser setzten zum Beispiel diese Technik beim HTTPS-Protokoll ein.

Hashfunktionen

Und zu guter Letzt gibt es noch kryptologische Hashfunktionen, die zum Beispiel sicherstellen sollen, dass ein digitales Objekt zwischenzeitlich nicht verändert wurde.
Jeder Text und jede Datei besitzt (in der Theorie) einen einmaligen Hashwert. Dies macht man sich bei Downloads zu Nutze. Lädt man sich ein Programm aus einer fremden Quelle herunter, kann man mittels geeignetem Tool (Ja, du wirst im 2. Teil erfahren, wie man das macht) überprüfen, ob die Datei unverändert ist (Stichwort: Malware).

Auch beim Abspeichern von Kennwörter haben sich Hashwerte bewährt. Anstatt diese im Klartext zu speichern, werden sie „gehasht“. Es ist nämlich schwierig, aus einem Hash das Kennwort zu berechnen. Gibt der Benutzer bei der Anmeldung ein Kennwort ein, wird von dem ein Hashwert erstellt und mit dem in der Datenbank verglichen. Sind die Werte gleich, ist der Benutzer erfolgreich authentifiziert.
Derzeit gilt noch die SHA-2-Familie zum Standard, auch wenn häufig noch SHA1 oder gar MD5 eingesetzt wird. Die SHA-3-Familie scharrt allerdings schon mit den Hufen.

Grundeinstellungen

Wie immer beginnt alles mit einem Projekt. Am besten erstellen wir ein Neues mit grafischer Oberfläche. Auf dieser platzieren wir drei Textboxen und fünf Buttons (siehe Bild) und geben allen Elementen eine sinnvolle Bezeichnung. Zu unserer using-Liste fügen wir System.Security.Cryptography hinzu.

Schlüsselerweiterung
Tabelle 1: Schlüssellängen

Alle symmetrischen Verfahren benötigen eine feste Schlüssellänge. Je nachdem welchen Algorithmus wir also einsetzen, muss der Schlüssel unter Umständen erweitert werden.

Da wir in unserem Beispiel AES verwenden werden, benötigen wir also einen 128, 192 oder 256 Bit langen Schlüssel. Das entspricht mit UTF8-Kodierung eine maximale Zeichenkette von 64 Zeichen. Zur Erinnerung: Der Schlüssel stellt das Kennwort dar. Folgende Methode erledigt diesen Job komfortabel für uns:

Der Methode übergeben wir den eingegebenen Schlüssel und die gewünschte Schlüssellänge. In der ersten Zeile erstellen wir ein neues Byte-Array und definieren die Länge. C# initialisiert uns das gesamte Array bereits mit Nullen.

In ein zweites, temporäres Byte-Array speichern wir den in UTF8-kodierten Schlüssel. Die Schleife sorgt nun dafür, dass jedes Zeichen aus dem temporären Array in unser neues Schlüssel-Array übertragen wird. Da dieses in der Regel größer ist als der eingegebene Schlüssel, bleiben am Ende noch stellen frei. Diese sind mit Nullen gefüllt. Das nennt sich Zero Padding und ist eine übliche Vorgehensweise um Schlüssel auf die richtige Schlüssellänge zu bringen. Ein anderes Verfahren wäre zum Beispiel auch das PKCS7 gewesen. Egal welches man bevorzugt, einen Zugewinn an Sicherheit bringt keines.

Initialisierungsvektor

Nun kümmern wir uns noch um den Initialisierungsvektor, der eine feste Blockgröße haben muss (siehe Tabelle 1). Dazu dient die vorherige Methode als Vorlage, die nur leicht modifiziert wird:

 

Auch hier erstellen wir zunächst ein Byte-Array mit der gewünschten Blockgröße. Die Schleife füllt dieses nun mit aufsteigenden Zahlen auf. Für die Sicherheit spielt die Geheimhaltung keine Rolle. Wichtig ist nur, dass die Nachricht mit dem gleichen Initialisierungsvektor ver- und entschlüsselt wird, sonst bekommen wir am Ende nur zusammenhanglose Zeichen raus.

Zeichenkette verschlüsseln

Da die Vorarbeit geleistet ist, beginnen wir damit, eine Zeichenkette zu verschlüsseln. Dazu wählen wir AES, ein symmetrisches Verfahren. Zur Erinnerung nochmal: Der Schlüssel ist für die Ver- und Entschlüsselung derselbe. Betrachten wir folgenden Quellcode:

 

Zeile 2 und 3 kommen uns bekannt vor. Nachdem wir in Zeile 1 eine Instanz des AES-Containers erstellt haben, übergeben wir ihm den Schlüssel (hier aus der Textbox) und den Initialisierungsvektor (AES erfordert eine Größe von 128 Bit = 16 Byte). Da AES einen 256 Bit-Schlüssel verlangt, erstellen wir demzufolge eine 32 Byte lange Zeichenkette (32 Byte = 256 Bit).

Anschließend werden zwei Streams instanziiert. Im Memorystream wird später das Ergebnis stehen. Der CryptoStream verschlüsselt alle Daten, die ihm übergeben werden. Da wir in diesen Stream schreiben wollen, teilen wir ihm den Modus entsprechend mit.

Als nächstes wird der Klartext UTF8-kodiert aus der Textbox in ein Byte-Array übergeben. cStream.Write bewirkt, dass dieser Klartext in den CryptoStream geschrieben wird. Die Methode FlushFinalBlock() ist wichtig, um den letzten Verschlüsselungsblock an den MemoryStream zu übertragen, auch wenn dieser noch nicht vollständig ist. Macht man das nicht, erhält man, wenn man die Verbindungen schließen möchte, eine Ausnahmemeldung, die besagt, dass das Padding ungültig ist. Diese Zeile spielt auch noch eine andere wichtige Rolle: Vergisst man sie, ist der verschlüsselte Stream 16 Bytes kürzer, als er sein sollte!

Bevor wir beide Streams schließen, wird der Memorystream Base64-kodiert in die Textbox geschrieben.

Führen wir nun das Programm aus, geben einen Schlüssel und einen Klartext ein, erhalten wir mit einem Klick auf den Verschlüsseln-Button die verschlüsselte Zeichenkette.

Zeichenkette entschlüsseln

Das Entschlüsseln von Zeichenketten ist genauso einfach wie das Verschlüsseln:

 

Hier gibt es jedoch einiges zu beachten:

  • Die zweite Zeile, also das Angeben des Paddings, umgeht eine Ausnahmemeldung, wenn der eingegebene Schlüssel zum Entschlüsseln kürzer ist, als der originale zum Verschlüsseln.
  • Anstatt des AESCrypto.CreateEncryptor() wird nun AESCrypto.CreateDecryptor() verwendet.
  • Ein try-catch-Block verarbeitet eine Ausnahmemeldung, wenn die Chiffre nicht einer Base64-Kodierung entspricht. Löst diese aus, ist das ein sicheres Zeichen dafür, dass das Entschlüsseln zu keinem Ergebnis führen wird.
  • Und schließlich sind die Byte-Arrays zusammen mit der Kodierung vertauscht.
Abschluss Teil 1

Das wars dann schon mit dem ersten Teil. Kommende Woche erscheint der zweite Teil, in dem wir Dateien ver- und entschlüsseln, sowie den Hashwert bilden werden. Auch das asymmetrische Verfahren werden wir uns etwas genauer anschauen. Abrunden werde ich den Artikel damit, dass ich einige allgemeine Tipps im Umgang mit Algorithmen gebe.

Bildquelle

[1]: Urheber ist Bananenfalter (Own work) [CC0], via Wikimedia Commons

2 Gedanken zu „Kryptographie mit C# – Teil 1

  • 17. Januar 2018 um 13:35
    Permalink

    Warum verschickst du dann Briefe, obwohl Postkarten günstiger sind?
    Antwort: Weil ich auf eine Postkarte keine DinA4 Seite voll schreiben kann.

    Antworten
    • 17. Januar 2018 um 14:28
      Permalink

      Mal davon abgesehen, dass deine Antwort aus dem Kontext dieses Artikels gerissen wurde, hast du sicher recht. Mit einem geeigneten Drucker könnte man den Platz einer Postkarte durchaus effizienter nutzen. Ich bin mir auch sicher, dass man einen Großteil des Briefverkehrs per Postkarte abwickeln könnte: Aktuellen Kontoauszug, 2. Mahnung aus Krediten mit Fristsetzung (Betrag, Kontonummer und Verwendungszweck bekommt man locker drauf), Kündigungsbestätigung des Viagra-Abos, …
      Ein darauf gedruckter temporärer Link könnte zu weiterführenden Informationen im Internet führen – natürlich unverschlüsselt, damit Google diese Informationen auch indexieren kann. Man könnte Milliarden für Porto sparen! Warum macht man das nicht? Richtig, es geht um Privatsphäre. Und genau damit befasst sich dieser Artikel.

      Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.