Immer wieder stößt man auf den Anwendungsfall, dass Benutzereingaben, die HTML enthalten dürfen, bereinigt werden müssen. Besonders hinsichtlich dem Schutz vor XSS Angriffen führt kein Weg an einem solchen Schritt vorbei. In PHP gibt es hierfür mehrere Bibliotheken wie HTML Purifier Bibliothek oder htmLawed.
Besteht die Anforderung, dass bestimmte HTML Eingaben erlaubt sein sollen, so ist der Einsatz einer Bibliothek an dieser Stelle unbedingt notwendig. Es gibt zahlreiche Angriffsmöglichkeiten, mit der ein Filter zurecht kommen muss und so kann man nur davon abraten selbst etwas zu bauen (z.b. Textersetzung mittels Regulären Ausdrücken).
Für Java habe ich eine sehr einfache und gute Bibliothek gefunden, die ich an dieser Stelle nur weiterempfehlen kann. jsoup heißt die schlanke Library, die unter der MIT Lizenz zur Verfügung steht und auch für die Manipulation von HTML Dokumenten zahlreiche Funktionen mit bringt. Zudem wird HTML5 und auch invalides HTML unterstützt.
Die Funktionsweise ist schnell erklärt. Will man jegliches HTML entfernen, so geschieht das mit folgenden Aufruf
String userText = "Beispieltext <script>alert('xyz');
</script> Beispieltext";
String cleanText = Jsoup.clean(userText, Whitelist.none());
// cleanText == "Beispieltext Beispieltext"
Wie man sieht, dient ein Whitelist Objekt zur Definition, was erlaubt ist und was nicht. So kann man genau festlegen, welche Tags und welche Attribute zugelassen werden und welche nicht. Folgendes Beispiel erlaubt nur <i> und <b> Tags:
String userText = "<a href='beispiel.de'>xyz</a>
Bla <i>Blub</i> Bl<b>ub</b>";
String[] allowedTags = {"i","b"};
Whitelist whitelist = new Whitelist();
whitelist.addTags(allowedTags);
String cleanText = Jsoup.clean(userText, whitelist);
// cleanText == "xyz Bla <i>Blub</i> Bl<b>ub</b>"
Die Dokumentation und das JAR File sind auf der Webseite von jsoup zu finden:
jsoup Website
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.
Besonders im Web-Umfeld ist das PDF Format ein beliebtes Mittel, da die Unterstützung durch Clients hervorragend ist. Häufig kommt man dann auch in die Verlegenheit mit PDF Dateien umzugehen und z.B. Dokumente on-the-fly zu erstellen oder zu editieren. In einem aktuellen Anwendungsfall sollen von PDF Dateien Vorschaubilder erzeugt und ausgegeben werden.
Hierfür habe ich die hervorragende Java Bibliothek Pdf-renderer entdeckt. Die unter LGPL stehende OpenSource Library ermöglicht es, PDF Dateien zu rendern und als Bild auszugeben. Ursprünglich wurde die Bibliothek von Sun Labs entwickelt, welche einen PDF Viewer für ihre mit OpenOffice erzeugten Ausgaben benötigten. 2007, als der Pdf-renderer nicht mehr benötigt wurde, hat man sich entschlossen das Projekt als OpenSource Lösung zu veröffentlichen (Quelle).
Einbinden
Der Pdf-renderer kann einfach als Maven Dependency eingebunden oder als JAR Datei heruntergeladen werden.
<dependency> <groupId>org.swinglabs</groupId> <artifactId>pdf-renderer</artifactId> <version>1.0.5</version> </dependency>
PDF laden und Bild erzeugen
Ein kurzes Codebeispiel zeigt, wie einfach aus einem PDF ein Bild erzeugt werden kann. In einem ersten Schritt wird aus der Datei als java.io.File Objekt ein ByteBuffer erzeugt. Dieser dient als Grundlage für den Zugriff auf den Inhalt der PDF Datei durch die pdf-renderer Klasse PDFFile.
import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFPage;
…
File file = new File("c:\\tmp\\test.pdf");
RandomAccessFile raf = new RandomAccessFile(file, "r");
FileChannel channel = raf.getChannel();
ByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, 0,
channel.size());
PDFFile pdffile = new PDFFile(buf);
EIn PDFFile Objekt liefert Informationen über die PDF Datei (Metadaten, Anzahl Seiten usw.). In einem zweiten Schritt kann dann auf die einzelnen Seiten zugegriffen werden.
int numPgs = pdffile.getNumPages(); PDFPage page = pdffile.getPage(0); Rectangle rect = new Rectangle( 0, 0, (int) page.getBBox().getWidth(), (int) page.getBBox().getHeight() ); Image img = page.getImage(rect.width, rect.height, rect, null, // null = kein ImageObserver true, // Hintergrund weiß true // synchron );
Es wird die erste Seite geholt und ein Image aus der Java 2D API erzeugt. Dieses kann dann beliebig weiter verarbeitet werden.
Zum Download der Bibliothek: http://java.net/projects/pdf-renderer