24.5 Verschlüsseln von Daten(-strömen)
 
Die Security-API von Java ist sehr unabhängig von kryptografischen Algorithmen und bietet zunächst Schnittstellen für Schlüssel und Implementierungen. Die konkreten Algorithmen wie RSA oder DES werden später als Provider eingebunden. Zum Teil sind sie das schon, aber immer wieder besteht der Wunsch, neue Implementierungen einzubringen, insbesondere, wenn uns die Amerikaner nicht erlauben, starke Verschlüsselung zu verwenden. Daher spaltet sich die API auch in die Java Cryptography Architecture (JCA) – die Basis – und die Java Cryptography Extensions (JCE), die die JCA erweitert.
24.5.1 Den Schlüssel bitte
 
Die Kryptografie unterscheidet zwischen asymmetrischer und symmetrischer Verschlüsselung. Ist die Kommunikation asymmetrisch, so werden zwei Schlüssel benötigt, ein öffentlicher und ein privater, und bei der symmetrischen Verschlüsselung ist nur ein Schlüssel nötig, der bei der Ver- und Entschlüsselung eingesetzt wird.
Jeder Schlüssel, sei es privat oder öffentlich, implementiert die Basisschnittstelle java.security.Key. Von dieser Schnittstelle gibt es Unterschnittstellen, etwa PublicKey, PrivateKey für die asymmetrischen Schlüssel oder SecretKey für den symmetrischen Schlüssel. Von diesen Schnittstellen gibt es dann auch wieder Unterschnittstellen.
Schlüssel aus der Fabrik
Um Schlüssel zu erzeugen, gibt es zwei Fabriken: KeyGenerator erzeugt symmetrische Schlüssel und KeyPairGenerator asymmetrische. Der Fabrikfunktion getInstance() ist dabei eine Kennung zu übergeben, die für den Algorithmus steht.
KeyGenerator kg = KeyGenerator.getInstance( "DES" );
KeyPairGenerator kpg = KeyPairGenerator.getInstance( "RSA" );
Der nächste Schritt sieht eine Initialisierung des Schlüssels mit zufälligen Werten vor. Ohne Initialisierung kann jeder Provider unterschiedlich verfahren.
kg.init( 56 ); // nicht größer als 56!
kpg.initialize( 1024 );
Schlau wie Sun, haben sie eine Funktion init() und die andere initialize() genannt. Toll. Beiden Funktionen lässt sich noch ein Zufallszahlengenerator mitgeben, doch intern ist das SecureRandom schon sehr gut. Kryptografische Parameter können über AlgorithmParameterSpec mit eingeführt werden.
Der letzte Schritt ist das Erfragen der Schlüssel.
SecretKey secKey = kg.generateKey();
KeyPair keyPair = kpg.genKeyPair();
Bei einer Ausgabe des symmetrischen Schlüssels über System.out.println() kommt nicht viel sinnvolles heraus, doch bei den privaten und öffentlichen Schlüssen, die keyPair mit getPublic() und getPrivate() offen legt, implementiert PublicKey und PrivateKey eine ansehnliche toString()-Funktion.
System.out.println( keyPair.getPublic() );
Liefert
SunJSSE RSA public key:
public exponent:
010001
modulus:
a8186ac3 03b9417e c0247c70 d225ae75 04d2fa3b 9b21e009 ca32a1f3 3cc7404f
aeb6df52 0aa4d9ab ae35a5d5 d7b30f38 ce670895 3234fab2 c67f1211 b9dab8d2
edda3a7b 710fbf86 0274a2a6 842c4d73 76fc2166 80ef1e82 36a949f9 8180c5c7
004cffdd c103b42b 9abf216d 5f797440 20b8ec52 afe44407 a871e1f7 0e27fec9
System.out.println(keyPair.getPrivate()) liefert eine noch länger Ausgabe mit Exponent, Modulo usw.
SecretKeySpec
Insbesondere ein symmetrischer Schlüssel wird nicht immer über die Fabrik erfragt, denn dann wäre er ja immer neu. Doch da beide Partner den Schlüssel kennen müssen, muss dieser ausgetauscht werden – ein Schwachpunkt dieser Verschlüsselungsart – und ein Key-Objekt muss mit einem Schlüssel vorinitialisiert werden.
Schlüssel sind nichts anderes als Binärfelder. Die Klasse javax.crypto.spec.SecretKeySpec dient zum Erzeugen eines symmetrischen Schlüssels und erwartet im Konstuktor den Schlüssel und den Algorithmus.
Key k = new SecretKeySpec( "01234567".getBytes(), "DES" );
Für andere Typen existieren wiederum andere Klassen. Es erzeugt DSAPrivateKeySpec zum Beispiel einen privaten Schlüssel aus dem privaten Schlüssel, zwei Primzahlen und einer Basis, gegeben als BigInteger-Objekte.
24.5.2 Verschlüsseln mit Cipher
 
Die Klasse javax.crypto.Cipher bildet das Zentrum der JCE. Nachdem mit init() das Objekt mit einem Modus und Schlüssel initialisiert wurde, lassen sich mit update(byte[]) Daten durchschleusen. doFinal() rundet das Ganze dann ab. Die Rückgabe ist immer ein verschlüsselter Block von bytes.
Cipher cipher = Cipher.getInstance( "DES" );
cipher.init( Cipher.ENCRYPT_MODE, key );
byte verschlüsselt[] = cipher.doFinal( unverschlüsselt );
Beim Entschlüsseln wird der Cipher einfach in den Modus Cipher.DECRYPT_MODE gesetzt.
24.5.3 Verschlüsseln von Datenströmen
 
Zum Verschlüsseln von Datenströmen bietet das Java-SDK die praktischen Klassen javax.crypto.CipherInputStream und CipherOutputStream an. Sie werden um ein Cipher-Objekt gebaut, was eine DES-Verschlüsselung durchführen soll.
Listing 24.6
WriteDES.java
import java.io.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class WriteDES
{
public static void main( String args[] ) throws Exception
{
Cipher c = Cipher.getInstance( "DES" );
Key k = new SecretKeySpec( "01234567".getBytes(), "DES" );
c.init( Cipher.ENCRYPT_MODE, k );
OutputStream out = new FileOutputStream( "C:/t.des" );
CipherOutputStream cos = new CipherOutputStream( out, c );
cos.write( "Das wird anders werden".getBytes() );
cos.close();
}
}
|