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.
Nach langer Zeit gibts mal wieder ein Wallpaper von mir. Wer für das neue Jahr also seinen Desktophintergrund erneuern will, kann hier zuschlagen.
Downloads
Über die Feiertage habe ich mich mal rangesetzt und für selfoss eine Android App geschrieben. Die App dient als Frontend und lädt im Hintergrund natürlich die Daten von der eigenen selfoss Installation. Daher muss beim Start der Applikation die URL, sowie die Zugangsdaten (sofern vorhanden) angegeben werden. Im Gegensatz zum WebFrontend, dass ja auch eine mobile Variante bietet, ist die App allerdings für die Bandbreite schonender (es werden lediglich die Daten via JSON übertragen) und in der Bedienung komfortabler.
Die App basiert auf phonegap und jQuery Mobile und ist natürlich auch wieder OpenSource unter github zu finden. Wen also die Umsetzung interessiert, der kann einen Blick darauf werden. Nachdem phonegap ja plattformunabhängig ist, könnte man sie auch ohne großen Aufwand für iOS, Windows Phone, usw. kompilieren. Allerdings benötigt man dazu einen iOS Entwickler Zugang und einen Mac. Wer also über beides verfügt, kann sich gerne bei mir melden.
Im Augenblick ist die App allerdings noch beta. Feedback und Fehler also bitte an mich weitergeben. Überhaupt interessiert mich eure Meinung zu selfoss und alles was dazu gehört. Über Rückmeldungen bin ich also sehr dankbar!
Zur Android App im Google Appstore: https://market.android.com/details?id=de.aditu.selfoss
Oder zum direkten Download der .apk Datei: http://selfoss.aditu.de/
In einem Gastbeitrag bei phphatesme schreibe ich über selfoss. Dort stelle ich auch die verschiedenen Bibliotheken vor, die ich für den RSS Reader verwende. Also für Interessierte: schaut einfach rein.
Herzlichen Dank an Nils, dass er mir die Möglichkeit gegeben hat, selfoss in seinem Blog vorzustellen.

















































