Als sicherheitsparanoider Nerd sicherheitsbewusster Entwickler kommt man um das Thema Verschlüsselung nicht herum. Besonders RSA in Kombination mit AES ist hier ein bewährtes Mittel um performant für eine sichere Verschlüsselung zu sorgen. Glücklicherweise bringt Java eine komfortable Kryptographie-Bibliothek mit, die eine einfache Integration in das Konzept der Java IO Stream bietet (mithilfe der mitgelieferten Klassen CipherInputStream bzw. CipherOutputStream). So kann ein beliebiger Stream verschlüsselt und entschlüsselt werden. Unabhängig davon, ob es sich um eine Netzwerkkommunikation, einen Dateizugriff oder das Verschlüsseln von serialisierter Objekte handelt: alles wird mit diesen Streams abgedeckt.
Das Vorgehen ist einfach: Ein OutputStream wird an den konfigurierten CipherOutputStream übergeben und alle darüber liegenden Schichten, welche den CipherOutputStream nutzen, profitieren implizit von der Verschlüsselung, ohne sich weiter darum Gedanken machen zu müssen.
Wer bereits mit RSA gearbeitet hat weiß, dass es sehr performancelastig ist, damit zu verschlüsseln. Trotzdem benötigt man oft eine Architektur mit asymmetrischen Schlüsseln (z.B. für Kommunikationsvorgänge). Daher werden die eigentlichen Daten mit einem zufällig erzeugten AES Schlüssel verschlüsselt und lediglich der AES Schlüssel wird mit RSA verschlüsselt und an den Empfänger übermittelt. Dieser entschlüsselt zuerst den AES Schlüssel mit dem RSA Algorithmus und kann dann im Anschluss mit dem weitaus effizienteren AES Algorithmus die eigentlichen Daten entschlüsseln.
Um das Vorgehen zu demonstrieren, habe ich ein kleines Java Beispiel geschrieben, dass einen kurzen Text verschlüsselt in einer Datei speichert und anschließend wieder ausliest. Erst den Quellcode, dann die Erklärung:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
public class EncryptionTest {
/**
* Zwischengespeichertes Schluesselpaar
*/
private static KeyPair keyPair;
/**
* Erzeugt ein RSA Schluesselpaar
*
* @return RSA Schluesselpaar
* @throws Exception
*/
public static KeyPair getKeyPair() throws Exception {
if (keyPair == null) {
KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
keyPair = kpg.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new Exception(
"Fehler beim Erzeugen des Schluesselpaars: "
+ e.getMessage());
}
}
return keyPair;
}
/**
* Wrapped einen OutputStream in einen verschlüsselnden CipherOutputStream
*
* @param os
* OutputStream
* @return verschlüsselter OutputStream
* @throws Exception
*/
public static OutputStream encryptOutputStream(OutputStream os)
throws Exception {
try {
// temporaeren AES Key erzeugen
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom random = new SecureRandom();
keygen.init(random);
SecretKey key = keygen.generateKey();
// mit RSA verschluesseln und an Empfaenger senden
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.WRAP_MODE, getKeyPair().getPublic());
byte[] encryptedAesKey = cipher.wrap(key);
os.write(encryptedAesKey);
// eigentliche Nachricht mit AES verschluesseln
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
os = new CipherOutputStream(os, cipher);
} catch (Exception e) {
throw new Exception("Fehler beim Verschluesseln: " + e.getMessage());
}
return os;
}
/**
* Liest den Inhalt einer Datei aus.
*
* @param file
* Dateiname
* @return Dateiinhalt (erste Zeile)
* @throws Exception
*/
public static String readFile(String file) throws Exception {
InputStream is = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader isrb = new BufferedReader(isr);
return isrb.readLine();
}
/**
* Wrapped einen InputStream in einen entschlüsselnden CipherInputStream
*
* @param is
* InputStream
* @return entschlüsselnder Inputstream
* @throws Exception
*/
public static InputStream decryptInputStream(InputStream is)
throws Exception {
try {
// AES Key lesen
byte[] wrappedKey = new byte[256];
is.read(wrappedKey, 0, 256);
// AES Key mit RSA entschluesseln
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.UNWRAP_MODE, getKeyPair().getPrivate());
Key key = cipher.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY);
// Daten mit AES entschluesseln
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
is = new CipherInputStream(is, cipher);
} catch (Exception e) {
throw new Exception("Fehler beim Entschluesseln: " + e.getMessage());
}
return is;
}
/**
* Beispiel für Verschlüsselung mit CipherInputStream und CipherOutputStream
*
* @param args
*/
public static void main(String... args) {
try {
String file = "c:/tmp/test.txt";
String plain = "test bla bla";
System.out.println("Klartext: " + plain);
// verschlüsseln
OutputStream os = new FileOutputStream(file);
os = encryptOutputStream(os);
OutputStreamWriter osw = new OutputStreamWriter(os);
osw.write(plain);
osw.close();
// verschlüsselten Text ausgeben
String secret = readFile(file);
System.out.println("verschluesselter Text: " + secret);
// entschlüsseln
InputStream is = new FileInputStream(file);
is = decryptInputStream(is);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader isrb = new BufferedReader(isr);
String decryptedPlain = isrb.readLine();
isrb.close();
System.out.println("entschluesselter Text: " + decryptedPlain);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
Zuerst wird ein FileOutputStream instanziiert und die Zieldatei angegeben. Dann wird mit encryptOutputStream dieser an einen CipherOutputStream übergeben. encryptOutputStream holt sich den privaten RSA Schlüssel aus dem Schlüsselpaar (das wird im Beispiel erst erzeugt), erzeugt einen zufälligen AES Schlüssel, verschlüsselt diesen AES Schlüssel mit RSA und sendet ihn über den gegebenen OutputStream (in diesem Fall ein FileOutputStream). Anschließend wird der CipherOutputStream mit dem AES Algorithmus instanziiert. Dieser CipherOutputStream kann dann genutzt werden, ohne dass man sich weiter mit Verschlüsselung auseinandersetzen muss (hier verwendet der OutputStreamWriter den CipherOutputStream wie einen gewöhnlichen OutputStream.
Analog läuft es mit dem decryptInputStream, der zuerst den AES Key ausliest, mit RSA entschlüsselt und anschließend den CipherInputStream mit dem AES Algorithmus erzeugt.
Entfernt man die encryptOutputStream und decryptInputStream Aufrufe, so entfällt die Verschlüsselung und alles läuft ohne wie gewohnt. Ähnlich einfach kann man diese Funktionen in bestehende Applikationen einfügen (natürlich muss man sich um eine Verteilung der Schlüssel noch kümmern, was allerdings nicht besonders schwer ist).
Ich hoffe, das Stück Code hilft den ein oder anderen weiter, der gerne Verschlüsselung einsetzen will, bisher aber die Mühen gescheut hat.
Hinterlasse eine Antwort