Eine Anwendung des SHA-512

Motivation

Für eine Webseite benötigte ich eine Verschlüsselungsmöglichkeit des Lokalteils von E-Mail Adressen. Dieselbe Technik lässt sich aber auch für andere Zeichenketten einsetzen.

E-Mail-Adressen wie Max.Mustermann@example.com bestehen prinzipiell aus drei Teilen: dem Lokalteil, dem Zeichen @ und dem Domänenteil. Über den Domänenteil wird der Dienstleister des zugehörigen E-Mail-Postfachs ermittelt. Der Lokalteil gibt an, welches Postfach bei diesem Dienstleister gemeint ist – oft benennt es den Empfänger sogar mit Namen. Der Lokalteil einer E-Mail-Adresse ist also spezifischer und privater als der Domänenteil – ebenso, wie Straße und Hausnummer privater als die Postleitzahl oder die Teilnehmerrufnummer privater als die Telefonvorwahl ist. Vorwahl oder Postleitzahl wird man beispielsweise für statistische Zwecke eher preisgeben als die zugehörigen persönlichen Angaben. Entsprechend ist es für bestimmte Anwendungen sinnvoll, den Lokalteil einer E-Mail-Adresse getrennt vom Domänenteil besonders zu schützen.

Ziele

Folgende Erwartungen stellte ich an die Verschlüsselung:

Umsetzung

Verfahren

Als Verschlüsselungsverfahren setze ich eine bitweise XOR-Verknüpfung von Schlüssel und erweitertem Klartext ein. Dabei wird ein Geheimtextbit genau dann „eingeschaltet“, wenn die entsprechenden Klartext- und Schlüsselbits unterschiedliche Werte haben.

Länge des Geheimtexts

Alle Klartexte werden vor ihrer Verschlüsselung auf die maximale Klartextlänge von 64 Bytes erweitert. So ergeben sich stets gleich lange Geheimtexte. Fürs Auffüllen der Klartexte werden ASCII-fremde Zeichen ergänzt – nach der Entschlüsselung lassen sich diese von den ASCII-Zeichen des Klartextes leicht trennen.

Identische Eingaben, unterschiedliche Ausgaben

Um selbst bei identischen Klartexten und identischen Passwörtern verschiedene Geheimtexte zu erzeugen, werden mehrere Maßnahmen getroffen:

Schlüsselwahl

Der Schlüssel wird gewonnen, indem die SHA-512-Funktion auf die Kombination aus Passwort und zufälligem 24-Bit-Wert angewendet wird. SHA ist ein englisches Akronym, dass für sicherer Hash-Algorithmus steht. Ein SHA-512-Hashwert besitzt eine länge von 512 Bits, also 64 Bytes – ideal für die Verknüpfung mit einem erweiterten Klartext derselben Länge.

Implementierung

Der folgende Quelltext zeigt eine mögliche Implementierung in PHP. Der Geheimtext wird für eine bessere Handhabung nicht in binärer Form, sondern zur Basis 64 kodiert ausgegeben und eingelesen, der zufällige 24-Bit-Wert auch in dieser Form verarbeitet. Dadurch ergibt sich eine Geheimtextlänge von 90 Bytes.

PHP-Quelltext

<?php

/* prlbr_64x7
*  is a class that en- and decrypts strings of up to 64 octets in the code range
*  0..127, e.g. the local parts of e-mail addresses. It uses a symmetric key
*  algorithm. Special characteristics are that all ciphertexts are equally long
*  and that same plaintexts encrypted using the same password still produce
*  different ciphertexts usually.
*/

class prlbr_64x7 {

    
/* base64_rand
    *  returns a string of a given $number of random characters from the base64
    *  alphabet.
    */
    
private static function base64_rand ($number) {
        static 
$base64 = array (
            
'A''B''C''D''E''F''G''H',
            
'I''J''K''L''M''N''O''P',
            
'Q''R''S''T''U''V''W''X',
            
'Y''Z''a''b''c''d''e''f',
            
'g''h''i''j''k''l''m''n',
            
'o''p''q''r''s''t''u''v',
            
'w''x''y''z''0''1''2''3',
            
'4''5''6''7''8''9''+''/'
        
);

        for (
$return ''$number 0; --$number):
            
$return .= $base64[mt_rand (063)];
        endfor;

        return 
$return;
    } 
// private static function base64_rand


    /* encrypt
    *  encrypts a $plaintext string of up to 64 octects in the code range 0..127
    *  using an arbitrary $password string and returns a string of 90 characters
    *  from the base64 alphabet.
    */
    
public static function encrypt ($plaintext$password) {
        
$textlen strlen ($plaintext);
        if (
$textlen 64):
            return 
false;
        endif;
        
// generate a 24-bit salt
        
$salt self::base64_rand (4);
        
// calculate the key as a hash from the salt and the password
        
$key hash ('sha512'$salt $passwordtrue);
        
// mix the plaintext octets with octects in the code range 128..255
        
for ($mixed ''$i 63$i >= 0; --$i):
            if (
mt_rand (0$i) < $textlen):
                
$mixed .= $plaintext[--$textlen];
            else:
                
$mixed .= chr (mt_rand (128255));
            endif;
        endfor;
        
// XOR mixed and key for encryption, base64-encode it, prepend the salt
        
return $salt substr (base64_encode ($mixed $key), 0, -2);
    } 
// public static function encrypt


    /* decrypt
    *  returns the plaintext after decrypting a given $ciphertext string of 90
    *  characters from the base64 alphabet using the provided $password string.
    */
    
public static function decrypt ($ciphertext$password) {
        
// extract the salt
        
$salt substr ($ciphertext04);
        
// base64-decode the ciphertext
        
$ciphertext base64_decode (substr ($ciphertext4) . '==');
        
// calculate the key as a hash from the salt and the password
        
$key hash ('sha512'$salt $passwordtrue);
        
// XOR ciphertext and key for decryption
        
$mixed $ciphertext $key;
        
// extract the octects in the code range 0..127 from the result
        
for ($plaintext ''$i strlen ($mixed) - 1$i >= 0; --$i):
            if (
ord ($mixed[$i]) < 128):
                
$plaintext .= $mixed[$i];
            endif;
        endfor;
        return 
$plaintext;
    } 
// public static function decrypt

// class prlbr_64x7

?>

Tipp zur Passwortwahl

Bei Einsatz in einer Datenbank kann es sich anbieten, den $password-Parameter zusätzlich zu variieren, beispielsweise durch Anhängen einer den Datensatz identifizierenden Nummer an das eigentliche Passwort.

Nicht-ASCII-Texte verschlüsseln

Beschrieben wurde, wie man bis zu 64 ASCII-Zeichen verschlüsseln kann. Das ist beispielsweise sinnvoll für die Sicherung von Lokalteilen von E-Mail-Adressen. In anderen Fällen mag es nützlich sein, auch Zeichen außerhalb des ASCII-Bereiches zu verschlüsseln, etwa in einer Passwortverwaltung, die auch Kennworte mit Umlauten speichert.

Für eine solche Anwendung kann ebenfalls der hier vorgestellte Algorithmus verwendet werden, wenn der Text vor der Übergabe an die Verschlüsselungsfunktion sowie nach der Entschlüsselung entsprechend umgewandelt wird. Erledigt man dies mit der Base64-Kodierung, lassen sich immerhin 48 völlig beliebige Klartext-Bytes verschlüsseln. Verwendet man eine effizientere Kodierung, die nicht nur ein Alphabet aus 64 Zeichen, sondern den gesamten ASCII-Bereich nutzt, lassen sich bis zu 56 beliebige Bytes verschlüsseln. In einem Folgeartikel beschreibe ich eine solche 8-Bit-7-Bit-Umwandlung.


  1. RFC 2822, Abschnitt 3.4.1, Abrufdatum 2011-02-10
  2. RFC 5321, Abschnitt 4.5.3.1.1, Abrufdatum 2011-02-10
  3. In der als Quelltext zur Verfügung gestellten PHP-Implementierung wird die Reihenfolge der Klartextzeichen im erweiterten Klartext umgedreht, aber nicht unnachvollziehbar durcheinandergewürfelt.