Donnerstag, 26. Februar 2015

Schlüssel mittels PBKDF2 aus Passwort erzeugen

Im vorigen Post habe ich die Verwendung der AES Verschlüsselung beschrieben. Dabei haben wir den benötigten Schlüssel und IV quasi manuell erzeugt, durch Konvertierung von von uns gewählten Strings in Byte Arrays. So sollte man es nicht machen. Deswegen hier eine Anleitung, wie man Schlüssel und IV auf relativ sichere Art und Weise wählt.
Dafür benutzen wir die Schlüsselableitungsfunktion PBKDF2. Diese ist in .Net in der Klasse Rfc2898DeriveBytes implementiert.
Zuerst wieder etwas zur Theorie: PBKDF2 steht für Password-Based Key Derivation Function 2. Sinn ist die Erzeugung einer Byte Folge (von der dann ein Teil als Schlüssel / IV verwendet werden kann) aus einem selbst gewählten Passwort. Die Byte Folge wird nach Initialisierung des Generators (u.A. mit dem Passwort Passwort) durch eine Pseudozufallsfunktion generiert und kann damit quasi beliebige Längen annehmen.
Statt nun also also ein Passwort zu wählen, und daraus z.B. mittels System.Text.Encoding.UTF8.GetBytes() eine Byte Folge als Schlüssel / IV zu erstellen, lassen wir dies nun durch die oben genannte Funktion erledigen. Was bringt uns das? Dafür müssen wir uns die Funktionsweise dieser anschauen.
Bei dem PBKDF2 Verfahren wird das gewählte Passwort mit einem gewählten Salt kombiniert und dann mehrfach gehasht. Dies dient als Grundlage des Zufallsgenerator, womit dann eine beliebig lange, pseudo zufällige Byte Folge erzeugt werden kann. Wir können / müssen also das Passwort, den Hash und die Iterationszahl auswählen. Dies hat etliche Vorteile gegenüber dem direkten Verfahren, hier einige davon (diese sind nicht in der Reihenfolge ihrer Wichtigkeit, sondern fast eher in umgekehrter Reihenfolge hinsichtlich dieser!):

  • Wie schon im vorigen Post festgestellt, kann die Konvertierung von String nach Byte Array ein Problem darstellen, da die Größe der Zeichen variiert und wir somit einen String der richtigen Länge verwenden müssen, um die Anforderung des z.B. AES Algorithus an Passwortlänge etc. einzuhalten. Mit PBKDF2 können wir beliebige Passwörter verwenden, und uns dann einfach die richtige Byte Anzahl aus dem Ergebnis ausgeben lassen.
  • U.a. das Hashen führt zu einer annähernden Gleichverteilung der Ergebnisbytes. Die direkte Byte Repräsentation selbst gewählter Passwörter (meistens Wörter oder Zahlen) ist oft vorhersehbar und damit der resultierende Schlüssel leichter durch Brute-Force knackbar.
  • Das mehrmalige Hashen erschwert und verlangsamt einen Brute-Force Angriff auf das Passwort, da der Angreifer nun nicht mehr nur das Passwort raten muss sondern zusätzlich auch die richtige Anzahl an Iterationen n, außerdem muss die Hashfunktion auch tatsächlich n mal angewandt werden.
  • Das zusätzliche Wählen eines Salts erschwert zusätzlich die Verwendung vorberechneter Rainbowtables.
Nun zum Code, die folgenden Zeilen berechnen aus dem gegebenen Passwort und dem Salt nach dem PBKDF2 Verfahren mit 10000 Iterationen eine Byte Folge. Mit der Funktion GetBytes(m) greifen wir die nächsten m Bytes aus der Byte Folge ab (in dem Fall 16 für AES 128 Bit):

Rfc2898DeriveBytes Generator = new Rfc2898DeriveBytes("Any password", System.Text.Encoding.UTF8.GetBytes("some salt"), 10000);
byte[] Key = Generator.GetBytes(16);
byte[] IV = Generator.GetBytes(16);

Mittwoch, 25. Februar 2015

AES (Rijndael) Verschlüsselung

In diesem Post möchte ich erklären, wie man mit C# die Rijndael Verschlüsselung benutzt. Diese ist eine symmetrische Blockchiffre, die nach DES als Verschlüsselungsstandard AES (Advanced Encryption Standard) eingeführt wurde. Bei einer symmetrischen Verschlüsselung wird zur Ver- und Entschlüsselung jeweils der gleiche Schlüssel eingesetzt.
Verschiedene Verschlüsselungsalgorithmen stehen in .Net in der Klasse System.Security.Cryptography zur Verfügung. Die Ver- oder Entschlüsselung erfolgt dann über einen CryptoStream, welcher ver- oder entschlüsselt und das Ergebnis in einen MemoryStream schreibt.
Zur Verschlüselung brauchen wir natürlich einen Schlüssel (Key). AES unterstützt die Schlüssellängen 128, 192 und 256 Bit. Der Schlüssel wird als Byte Array übergeben, ich werde jedoch im Beispielprogramm einfach einen String nehmen und diesen mittels System.Text.Encoding.UTF8.GetBytes() in ein Byte Array umwandeln. Dies ist insofern problematisch, als dass je nach Zeichen UTF8 1 - 4 Bytes zur Kodierung verwendet und keine fixe Größe, je nach Zeichenfolge kann der Schlüssel also eine ungültige Länge haben. Deswegen gibt es bessere Methoden, einen Schlüssel zu erzeugen, hier soll diese aber genügen, da die Standardzeichen alle durch 1 Byte dargestellt werden.
Blockchiffren brauchen außerdem noch einen Initialisierungsvekor (IV). Dies kommt daher, dass der zu verschlüsselnde Text in Blöcke gleicher Größe aufgeteilt wird, die dann separat verschlüsselt werden. Damit man aus dieser Struktur keine Informationen ableiten kann, wird ein IV verwendet, um den ersten Block zu maskieren und aufbauend darauf die Verschlüsselung der weiteren zu randomisieren (siehe http://de.wikipedia.org/wiki/Betriebsmodus_(Kryptographie)). Für AES erlaubte Blockgrößen sind ebenfalls 128, 192 und 256 Bit. Die Länge des IV muss eine Blockgröße durch 8 dividiert sein.
Nehmen wir hier die kleinste Schlüssellänge, 128 Bit. Angenommen, jedes Zeichen wird mit einem Byte kodiert, also brauchen wir einen Schlüssel der Länge 16. Als IV nehmen wir ebenfalls einen String der Länge 16.
Nun zum Code, welcher nach den vorigen Erklärungen meiner Meinung nach recht gut verständlich sein sollte:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Security.Cryptography;
using System.IO;

namespace AES
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // legal key sizes are 128, 192 or 256 bit
            // legal block sizes are 128, 192 or 256 bit
            // IV has to be of length blocksize / 8

            string Key = "1234567812345678";
            string IV = "0000000000000000";

            byte[] Ciphertext = Encode("This is a secret.", Key, IV);
            string Plaintext = Decode(Ciphertext, Key, IV);

            textBox1.Text = System.Text.Encoding.UTF8.GetString(Ciphertext);
            textBox2.Text = Plaintext;
        }

        public string Decode(byte[] encryptedBytes, string key, string IV)
        {
            Rijndael AESCrypto = Rijndael.Create();
            AESCrypto.Key = System.Text.Encoding.UTF8.GetBytes(key);
            AESCrypto.IV = System.Text.Encoding.UTF8.GetBytes(IV);

            MemoryStream ms = new MemoryStream();
            CryptoStream cs = new CryptoStream(ms, AESCrypto.CreateDecryptor(), CryptoStreamMode.Write);

            cs.Write(encryptedBytes, 0, encryptedBytes.Length);
            cs.Close();

            byte[] DecryptedBytes = ms.ToArray();
            return System.Text.Encoding.UTF8.GetString(DecryptedBytes);
        }

        public byte[] Encode(string plaintext, string key, string IV)
        {
            Rijndael AESCrypto = Rijndael.Create();
            AESCrypto.Key = System.Text.Encoding.UTF8.GetBytes(key);
            AESCrypto.IV = System.Text.Encoding.UTF8.GetBytes(IV);

            MemoryStream ms = new MemoryStream();
            CryptoStream cs = new CryptoStream(ms, AESCrypto.CreateEncryptor(), CryptoStreamMode.Write);

            byte[] PlainBytes = System.Text.Encoding.UTF8.GetBytes(plaintext);
            cs.Write(PlainBytes, 0, PlainBytes.Length);
            cs.Close();

            byte[] EncryptedBytes = ms.ToArray();
            return EncryptedBytes;
        }

    }
}

Eine Anmerkung noch: Die Funktion Encode() gibt ein Byte Array und keinen String zurück. Dies liegt daran, dass die entstandenen Bytes Sonderzeichen ergeben und somit bei der Konvertierung nach String und zurück zu Byte Array ihre Größe ändern und somit den String für den Algorithmus unleserlich machen.