Ein Ausflug in die Kryptologie

Tags:

Letzte Woche musst ich zum ersten Mal in meinem Programmierertum Daten ver- und auch wieder entschlüsseln. Bisher kam ich nur mit Einwegfunktionen zur Berechnung von Prüfsummen oder zum Hashen von Passwörtern in Berührung. Hier ein Abriss meiner Entdeckungen.

Klären wir ein paar Begriffe

Die Kryptologie ist die Wissenschaft, die sich mit der Sicherheit von Informationen beschäftigt. Darunter fallen aber nicht nur klassiche Themen wie Geheimschriften, Verschlüsselungsverfahren, sondern auch elektronische Wahlen oder z.B. BitCoins. Die Kryptologie ist unterteilt in die zwei Fachgebiete Kryptografie (Wie schütze ich Informationen? Was ist wann sicher?) und Kryptanalyse (Wie gewinne ich Informationen aus verschlüsselten Daten? Wie sicher ist ein Algorithmus?).

Ein Algorithmus (algorithm) ist ein klar definierter Ablauf von immer gleichen Handlungen. Meist um ein Problem zu lösen. Yay, solche Definitionen machen Spass, nicht?! Ein kryptografischer Algorithmus, auch Chiffre (cipher) wird verwendet, um Klartext (plaintext) mit Hilfe eines geheimen Schlüssels (key) zu verschlüsseln, bzw. den daraus resultierende Geheimtext/Chiffretext (ciphertext) auch wieder zu entschlüsseln. Die Steganografie (steganography) versucht im Gegensatz zur Kryptografie nicht, die Informationen zu verdecken, sondern die Kommunikation/den Kommunikationskanal zu verbergen. Die beiden Verfahren lassen sich kombinieren.

Alle möglichen Schlüssel einer Chiffre werden als Schlüsselraum bezeichnet; die Anzahl davon als Schlüsselanzahl. Berechnet man davon den Logarithmus zur Basis 2 (log2), erhält man die Schlüssellänge (key size) in bit. Je grösser die Schlüssellänge ist, desto sicherer kann ein Algorithmus theoretisch sein (da es schlicht mehr Möglichkeiten gibt, um den Geheimtext zu entschlüsseln).

Ein paar Begriffe kommen noch, da müsst ihr jetzt durch. Los geht's...

Symmetrisch und asymmetrisch

Chiffren lassen sich in generell unterteilen in asymmetrische und symmetrische Verfahren. Bei Ersterem besitzen alle Kommunikationspartner ein eigenes Schlüsselpaar (key pair), bestehend aus einem öffentlichen Schlüssel (public key) und einem geheimen Schlüssel (private key). Sie haben den grossen Vorteil, dass kein geheimer Schlüssel zwischen den Partnern ausgetauscht werden muss. Bei symmetrischen Chiffren sind die Schlüssel der kommunizierenden Parteien entweder gleich, oder lassen sich voneinander ableiten. Symmetrische Verfahren werden unterteilt in Blockchiffre (block cipher) und Stromchiffre (stream cipher). Bei der Blockverschlüsselung wird der Klartext zuerst in gleich grosse Blöcke aufgeteilt. Hat der letzte Block nicht die korrekte Grösse, wird er normalerweise mit z.B. Nullbytes aufgefüllt. Dieser Vorgang nennt sich Padding. Danach werden alle Blöcke einzeln verschlüsselt und am Schluss aneinandergereiht. Die Blocklänge (block size) wird in bit angegeben und sagt aus, wie lang jeder einzelne Block ist. Grössere Blöcke sind sicherer, da sie die statische Analyse von einzelnen Blöcken erschweren. Stromverschlüsselung hingegen, verschlüsselt jedes Bit, Byte oder Zeichen (je nach Implementation) einzeln. Dabei verändert sich der Schlüssel nach jedem solchen Vorgang. Deshalb erhält man für eine Klartext/Schlüssel-Kombination stets einen anderen Geheimtext.

Gleich haben wir es! Nochmals durchatmen. Und los!

Modes of Operation

Bei Blockchiffren wird der Algorithmus mit Hilfe kryptographischer Modi (modes of operation) auf die aufgeteilten Blöcke angewandt. Diese Modi sind unabhängig von der eingesetzten Chiffre. Damit der Verschlüsselungsprozess Zufälligkeit enthält, benötigen einige dieser Modi einen sogenannten Initialisierungsvektor (initialization vector). Man kann sich diesen wie das Salz beim Hashing vorstellen. Der IV sorgt dafür, dass der Chiffretext bei gleicher Klartext/Schlüssel-Kombination nie gleich ist. Dadurch wird statische Analyse vorgebeugt. Wie das Salz muss auch der IV immer mitgespeichert werden, da Ver- und Entschlüsselung den selben benötigen. Er muss wie das Salz nicht geschützt werden.

Werfen wir einen Blick auf drei weitverbreitete Modi.

ECB - Electronic Code Book

ECB

ECB ist ziemlich einfach: Es wird jeder Block einzeln mit dem Schlüssel verschlüsselt. Das Problem ist, dass man so für jeden Klartext immer den selben Chiffretext erhält. Da die Blöcke in keiner Abhängigkeit stehen, lassen sie sich verschieben, ohne das dies bemerkt werden kann. ECB sollte deshalb nur für kleine, zufällige Daten verwendet werden. Je grösser die Daten, desto mehr Blöcke, desto einfacher die statische Analyse, desto unsicherer die Verschlüsselung mit ECB.

CBC – Cipher Block Chaining

CBC

Bei CBC wird für die Verschlüsselung eines Blockes immer der vorangehende, verschlüsselte Block zusätzlich eingebunden. Für den ersten Block wird ein IV genutzt. Dadurch erhält der Chiffretext Zufälligkeit und ist bei gleicher Klartext/Schlüssel-Kombination nie gleich. Da ein Block stets von vorherigen Block abhängt, werden Bitfehler weitergereicht und alle Folgeblöcke affektiert. Allerdings ist CBS selbstsyncronisiert. D.h., falls ein kompletter Block verloren geht, kann die Entschlüsselung trotzdem fortgesetzt werden. CBC ist wesentlich sicherer als ECB und kann als Standard für "normale" Verschlüsselung von Dokumenten, Videos, etc. angesehen werden.

OFB - Output Feedback

OFB

Bei OFB ist der Startpunkt ein IV. Dieser wird mit dem Schlüssel verschlüsselt, wobei dieses Resultat a) als als "IV" für den nächsten Block gilt und b) XOR mit dem Klartext verknüpft den Chiffretext ergibt. Da die eingesetzte Chiffre nicht vom Klartext abhängig ist, lassen sich die ganzen Bithaufen, die anschliessend blockweise mit den Klartextblöcken XOR verknüpft werden, bereits im Voraus berechnen. Das kann ein grosser Vorteil bei zeitintensiven Verschlüsselungen sein. Anzumerken ist noch, dass Ver- und Entschlüsselung genau gleich funktionieren und OFB auch als Stromverschlüsselung eingesetzt werden kann.

Gängige Chiffren

Schauen wir uns jetzt ein paar gängige symmetrische Chiffren an. Auf asymmetrische Verfahren werde ich in diesem Artikel nicht weiter eingehen. Google ist euer Freund.

Name Beschreibung Sicher? Schlüssellänge Blocklänge Runden
DES (Data Encryption Standard) alt, langsam nein 56 bit 64 bit 16
3DES (Triple DES) drei Instanzen von DES hintereinander, drei Mal so langsam ja 112 bit 64 bit 16
AES (Advanced Encryption Standard) Eigentlich Rijndael, Gewinner beim Wettbewerb für AES, Standard bei der US Regierung ja 128/192/256 bit 128 bit 10/12/14
Blowfish public domain ja 32 - 448 bit 64 bit 16
Twofish Nachfolger von Blowfish, nebst Rijndael Finalist bei AES, public domain ja 128/192/256 bit 128 bit 16

Daneben gibt es noch etliche weitere Chiffren, die den Rahmen dieses Artikels aber sprengen würden. Meine Empfehlung ist, entweder AES oder Twofish zu benutzen. Was ihr nicht tun solltet, ist, unbekannte oder gar eigene Chiffren einzusetzen. Algorithmen müssen von einer breiten Öffentlichkeit begutachtet und getestet worden sein. Sie müssen sich bewähren. Und Fehler geschehen nun mal schnell.

Ein Praxisbeispiel

Ein Beispiel in PHP 5.3+ mit AES-256 und OFB. Ich sollte wohl erwähnen, dass ich keinerlei Verantwortung übernehme, wenn ihr mit diesem Code-Schnippsel Daten verschlüsselt. Es gibt bestimmt auch komplette mcrypt-Klassen für PHP. Einfach mal bei GitHub suchen... :)

<?php
namespace BigWhoop\Crypto;

class Cipher
{
    const PADDING_CONTROL_CHAR = "\1";

    /**
     * @var string|null
     */
    private $cipher = null;

    /**
     * @var string|null
     */
    private $mode = null;

    /**
     * @var resource
     */
    private $handle = null;


    /**
     * @param string $cipher
     * @param string $mode
     */
    public function __construct($cipher, $mode)
    {
        $this->cipher = $cipher;
        $this->mode   = $mode;
        $this->handle = mcrypt_module_open($cipher, '', $mode, '');
    }


    public function __destruct()
    {
        mcrypt_module_close($this->handle);
    }


    /**
     * @param string $passphrase
     * @param string $iv
     * @return Cipher
     */
    public function init($passphrase, $iv)
    {
        mcrypt_generic_init($this->handle, $passphrase, $iv);
        return $this;
    }


    /**
     * @return Cipher
     */
    public function deinit()
    {
        mcrypt_generic_deinit($this->handle);
        return $this;
    }


    /**
     * @param string $passphrase
     * @param string $iv
     * @param string $data
     * @return string
     */
    public function encrypt($passphrase, $iv, $data)
    {
        $data .= self::PADDING_CONTROL_CHAR;

        $this->init($passphrase, $iv);
        $data = mcrypt_generic($this->handle, $data);
        $this->deinit();

        return $data;
    }


    /**
     * @param string $passphrase
     * @param string $iv
     * @param string $data
     * @return string
     */
    public function decrypt($passphrase, $iv, $data)
    {
        $this->init($passphrase, $iv);
        $data = mdecrypt_generic($this->handle, $data);
        $this->deinit();

        $data = rtrim($data);
        $data = substr($data, 0, 0 - mb_strlen(self::PADDING_CONTROL_CHAR));

        return $data;
    }


    /**
     * @return string
     */
    public function createInitializationVector()
    {
        $size = mcrypt_get_iv_size($this->cipher, $this->mode);
        return mcrypt_create_iv($size, MCRYPT_RAND);
    }
}

Anwendung

<?php
use BigWhoop\Crypto\Cipher;

$cipher = new Cipher(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_OFB);

$passphrase = 'gurkensalat';
$initVector = $cipher->createInitializationVector();
$plaintext  = 'super geheimer text';

// Verschlüsseln
$ciphertext = $cipher->encrypt($passphrase, $initVector, $plaintext);

// Entschlüsseln
echo $cipher->decrypt($passphrase, $initVector, $ciphertext);
// -> super geheimer text

Ich hoffe ihr konnte was lernen. Bis demnächst!

Kommentare