Ich hatte ja bereits in einem früheren Beitrag erwähnt, dass ich mich daran versucht habe den RSS Reader rsslounge lernfähig zu machen. Dazu sollte der Benutzer neue Beiträge als interessant oder uninteressant klassifizieren können. Ausgehend von der Bewertung dieser bestehenden Einträgen durch den Benutzer, sollten dann neue Feed-Beiträge automatisch weiter nach oben (Interessantes) oder nach unten (Uninteressantes) einsortiert werden.

Hierfür habe ich verschiedene Verfahren der Test-Klassifikation aus dem Information Retrieval in Betracht gezogen und hinsichtlich ihrer Eignung bezüglich des Problems untersucht. Anschließend habe ich als Referenz die Lernfähigkeit mittels dem naiven Bayes-Klassifikators implementiert. Als zweite Lösung habe ich das k-nächste-Nachbarn Verfahren, basierend auf dem simulierten Abkühlen implementiert und untersucht, ob eine der beiden Lösungen für dieses Problem in Frage kommt. Das Ergebnis ist ist leider sehr ernüchternd.

Trotzdem möchte ich hierzu das Paper, das ich dazu verfasst habe, veröffentlichen. Zum einen gibt es einen guten Überblick und eine gute Einführung in die gängigen Verfahren der Text-Klassifikation, zum anderen halte ich das optimierte k-nächste-Nachbarn Verfahren für sehr leistungsfähig. Für passende Problemstellungen ist das ein praxistauglicher Lösungsweg.

Nachdem ich die gesamte Entwicklung von rsslounge offen gelegt habe, halte ich es für sinnvoll auch diesen Versuch (und das damit verbundene Paper) zu veröffentlichen. Vielleicht inspiriere ich jemanden, oder liefere für weitere Arbeiten eine Grundlage bzw. Informationen.

Das Paper als PDF zum Download (1.5MB): Automatisiertes Priorisieren von RSS Feed Beiträgen mittels maschinellem Lernen

Jetzt ist es schon wieder einige Zeit her, dass ich hier etwas geschrieben habe und ich denke ich gebe mal ein Lebenszeichen von mir. Natürlich gibt es zahlreiche interessante Themen in meiner Queue zum bloggen, aber wie immer findet man nicht die Zeit oder nötige Ruhe dazu.

Die Problemstellung

Nun aber zum Thema: für ein aktuelles Projekt benötige ich, von meiner PHP Webapplikation heraus, den Zugriff auf einige Hardwarekomponenten. Zudem will ich einige Fremdbibliotheken verwenden, die sehr rechenintensive Aufgaben übernehmen und auch nur in C++ zur Verfügung stehen. Die Frage ist also: wie kann ich diese Komponenten an mein PHP Programm ankoppeln?

Für Java gibt es eine ganz gute Server Bridge, die innerhalb der Java Virtual Machine einfach einen kleinen Server startet, der XML Anfragen entgegen nimmt, stellvertretend ausführt und das Ergebnis, auch wieder über eine XML Kommunikationsschnittstelle, zur Verfügung stellt. Die nötige PHP Klasse wird ebenfalls durch den Java Server direkt zur Verfügung gestellt. Eine ähnliche Bridge Lösung bringt auch der Zend Server bereits out of the box mit.

Für C++ habe ich so eine Lösung nicht gefunden und als weiteren Ansatz überlegt, eine PHP Extension zu entwickeln. Sowohl für Linux, als auch für Windows, gibt es hier Tutorials (siehe “Extension-Entwicklung unter WAP“, “Wrapping C++ Classes in a PHP Extension” oder “Extension Writing Part I: Introduction to PHP and Zend“). Allerdings ist, neben den Tutorials, der ganze Vorgang nicht gut dokumentiert und scheinbar muss das Kompilat der Extension exakt dem des verwendeten PHP Kompilats entsprechen. Insgesamt also eher eine wackelige Angelegenheit.

Lösungsansatz

Meine Lösung ist hingegen pragmatisch und verfolgt eine eher losere Kopplung: Meine C++ Komponenten werden in ein eigenständiges Programm ausgelagert, das selbst, über einen gewöhnlichen HTTP Server, die benötigten Dienste zur Verfügung stellt. Dabei orientiere ich mich an dem REST Architektur Prinzip und binde jeweils eine Teil-Funktionalität an eine feste URL. Die Parameter und Rückgabewerte werden mittels JSON kodiert. Von PHP Seite aus, wird der Aufruf der C++ Komponenten sauber gekapselt, so dass für das PHP Programm der Eindruck entsteht, dass eine gewöhnliche Funktion aufgerufen wird.

Welche Vor- und Nachteile hat das Vorgehen? Zum einen ist die in C++ implementierte Funktionalität sauber gekapselt und von der Webapplikation getrennt. Dadurch wird die Software leichter wartbar. Durch eine saubere Schnittstellendefinition, kann die C++ Serverkomponente auch unabhängig vom Wissen über die PHP Applikation gepflegt werden. Zum anderen entsteht aber ein zusätzlicher Aufwand (mit zusätzlichen Fehlerquellen). Die Schnittstelle muss definiert und implementiert werden, ein Fehlerhandling muss durchgeführt werden und eine zusätzliche Serverapplikation muss gestartet werden, was hinsichtlich der Sicherheit berücksichtigt werden muss. Dennoch halte ich die Lösung für die eleganteste, denn sie nutzt genau die Vorteile der Vernetzung und der losen Kopplung aus. So könnte die C++ Komponente bei höherer Last auch leicht auf einen eigenen Server umgezogen oder unabhängig von der Webapplikation betrieben werden.

Implementierung

In der Theorie hört sich natürlich immer alles ganz nett an, aber wie sieht das mit dem HTTP Server aus? Hier gibt es eine hervorragende C++ Bibliothek, mit der sich ohne großen Aufwand eine solcher HTTP Server realisieren lässt. mongoose heißt das Wunderding, steht unter der MIT Lizenz unter Google Code zur Verfügung. Es unterstützt Windows, Linux und Mac OS X, bietet eine CGI Schnittstelle, SSL Verschlüsselung, ACL, eine saubere API und ist nicht größer als 60 kB. Die Dokumentation ist zwar recht knapp, aber dank Beispiele findet man sich schnell zurecht und auch die API Beschreibung ist vollkommen ausreichend.

Will man einen eigenen Server starten, so muss zuerst ein neuer Context erzeugt werden:

struct mg_context* ctx;
ctx = mg_start();

Mit mg_set_options, werden die Servereinstellungen geändert. Folgende zwei Zeilen deaktivieren das Directory Listing und setzten 8080 als Port.

mg_set_option(ctx, "dir_list", "no");
mg_set_option(ctx, "ports", "8080");

Aus Sicherheitsgründen erlaube ich nur einen Zugriff vom selben Rechner aus, also erlaube nur die IP Adresse 127.0.0.1:

mg_set_option(ctx, "acl", "-0.0.0.0,+127.0.0.1");

Mit mg_set_uri_callback kann ein Pointer auf eine eigene Funktion an eine URL gebunden werden. Diese wird aufgerufen, wenn der Client die URL aufruft, wobei die URL auch Wildcards enthalten kann.

mg_set_uri_callback(ctx, 'dosomething', &Server::dosomething, NULL);

Als dritter Parameter darf ein Pointer (void*) auf ein beliebiges Objekt übergeben werden. Die Callback Methode bekommt dazu alle notwendigen Parameter, wie die aktuelle Verbindung, den aktuellen Request und das zuvor übergebene Objekt (hier NULL) übergeben.

void Server::capture(struct mg_connection *conn,
 const struct mg_request_info *request_info,
 void *user_data) {
char* param = mg_get_var(conn, "param");
mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n");
}

Mit mg_printf kann dann eine Antwort an den Client gesendet werden. Hier als einfaches Beispiel einfach nur ein OK (HTTP 200). In meinem Szenario wird hier der generierte JSON Text zurück gegeben. Mit mg_get_var können übergebene Parameter (POST, GET) ausgelesen werden. Wichtig: um memory leaks zu vermeiden, müssen diese mit mg_free wieder freigegeben werden.

Natürlich ist das nur die eine Hälfte. PHP muss jetzt auch Funktionen, auf dem Server, aufrufen können. Dazu kommt der Zend_Http_Client zum Einsatz. Dieser ist im Zend Framework enthalten, mit etwas Frickelei kann er aber auch aus dem Gesamtpaket heraus gepickt werden. Da ich aber das Zend Framework für die ganze Anwendung nutze, ist das Aufrufen der URL und das Decodieren des JSON Strings in einen Action Helper ausgelagert, der gleichzeitig eine einfache Fehlerbehandlung durchführt:

class JsonHttpHelper extends Zend_Controller_Action_Helper_Abstract {
   public function get($url, $params = array()) {
      try {
         $client = new Zend_Http_Client($url);
         foreach($params as $param=>$value)
            $client->setParameterPost($param, $value);
            $response = $client->request('POST');
            $res = Zend_Json::decode($response->getBody());
            if($response->isSuccessful()) {
               return $res;
            } else {
               return array("error" => "request not successfully: ".$res["error"]);
            }
      } catch(Exception $e) {
         return array("error" => $e->getMessage());
      }
 }
}

Bisher bin ich mit der Lösung sehr zufrieden. Besonders mongoose kann ich nur wärmstens empfehlen. Auch von der Verarbeitungsgeschwindigkeit ist das Ergebnis zufriedenstellend und kann sich sehen lassen. Um den Rahmen nicht zu sprengen, habe ich die serverseitige Generierung und Verarbeitung von JSON außen vor gelassen. Hier gibt es aber auch zahlreiche Bibliotheken, so dass die Suche nicht schwer fallen dürfte.

Ich habe in diesem Blog ja bereits einmal darüber geschrieben, wie man mit Hilfe des Zend Frameworks fremde Seiten verarbeiten kann. Einen zweiten, sehr eleganten Weg, wie man fremde HTML Seiten verarbeiten kann, bietet die Bibliothek phpQuery. Diese greift zwar auch auf das Zend Framework zurück, bringt davon aber nur zwei Teilkomponenten mit und ist dadurch viel schlanker.

phpQuery greift die Vorgehensweise von jQuery auf und bietet eine zentrale Funktion an, mit deren Hilfe DOM Elemente in einem Dokument selektiert werden können. Beispielsweise liefert die folgende Funktion alle ‘ul’ Elemente.

$uls = pq('ul');

Mit phpQuery hat man natürlich auch die Möglichkeit, wie bei jQuery auch DOM Elemente zu manipulieren, zu löschen oder neue einzufügen.

Will man also beispielsweise alle Suchergebnisse von Google abgreifen, so reicht folgender Code aus, der dann alle gefundenen URLs ausgibt:

require('phpQuery/phpQuery.php');
$q = "phpquery";
$doc = phpQuery::newDocument(
	file_get_contents('http://www.google.com/search?q='.urlencode($q))
);
foreach(pq('.g', $doc) as $entry) {
	$url = pq($entry)->find('.r a')->attr('href');
	$title = pq($entry)->find('.r a')->html();
	echo '<a href="'.$url.'">'.$title.'</a><br />';
}

Mir gefällt der Ansatz, die Funktionalität von jQuery teilweise in die Welt von PHP zu übertragen. Jeder, der in irgendeiner Form HTML in PHP verarbeiten muss, sollte sich diese Bibliothek auf jeden Fall genauer ansehen.

Heute feiert der populäre phphatesme Blog seinen 500. Beitrag und präsentiert sich im Zuge des Jubiläums in einem neuen Design. Mir gefällt das neue Layout sehr gut, es ist schlicht und übersichtlich. Natürlich gibt es auch ein Gewinnspiel mit interessanten Bücher, Eintrittskarten und Zeitungsabos als Preise.

Wer meinen Blog liest, kennt phphatesme bereits von meinem Gastbeitrag über rsslounge. Jedem anderen empfehle ich einen Blick auf den Blog zu werfen, der täglich interessante Themen aus der Softwareentwicklung mit PHP aufgreift.

Jede sauber programmierte PHP Applikation macht es notwendig, dass Klassen dynamisch  nachgeladen werden müssen. Dazu bietet PHP die __autoload() Funktion. Diese wird im Zend Framework natürlich in einem Objekt gekapselt. Nachdem ich den einfachen Weg immer vorziehe, nutze ich natürlich diesen gewöhnlichen Autoloader Mechanismus durch die Klasse Zend_Loader_Autoloader:

$autoloader =  Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('application_');

Wird die Zend_Application Klasse verwendet, so wird der Autoloader automatisch aktiviert (die Namespace’s müssen dabei in der Konfiguration gesetzt werden). Verwende ich dann eine Klasse, so wird der Name entsprechend aufgelöst, d.h. zu obigen Beispiel wird bei der Verwendung der Klasse “application_models_items” die Datei “application/models/items.php” eingebunden. Das Problem bei dieser Lösung: hat man ein sehr großes Projekt mit einer tiefen Hierachie, dann wächst der Klassenname entsprechend stark an. Bei meinem RSS Reader rsslounge hab ich das beispielsweise in Kauf genommen, da die Struktur nicht komplex ist. Zudem sieht man am Namen schon sofort, wo die Datei abgelegt ist und welche Funktion die Klasse übernimmt (application_models_items ist ein Model, dass sieht man sofort).

Seit der Version 1.8 bietet das Zend Framework einen eleganten Weg Klassen automatisch zu laden, dabei aber genau zu spezifizieren, wo die Klassen gesucht werden sollen. Dazu wird ein Namespace definiert und für verschiedene “Unter-Namespace” Pfade angegeben. Ein Beispiel veranschaulicht das Vorgehen:

$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
 'basePath'  => APPLICATION_PATH . '/../einordner',
 'namespace' => 'Myproject',
 ));
 $resourceLoader->addResourceType('models', 'models', 'Model');
 $var = new Myproject_Model_Xyz();

Zuerst wird die Klasse Zend_Loader_Autoloader_Resource instanziiert. Dabei wird ein Namespace angegeben (im Beispiel “Myproject”) und definiert, in welchem Ordner die zugehörigen Dateien zu finden sind. In einem zweiten Schritt wird dann mit addResourceType ein “Unter-Namespace” definiert und angegeben in welchem Unterverzeichnis die Klassen liegen. Der Aufbau von addResourceType ist wie folgt:

 $resourceLoader->addResourceType($name, $ordner, $namespace);

Damit ist der Autoloader für alle Klassen mit dem Präfix “Myproject_Model_” aktiv. Im Beispiel wird eine Klasse Xyz geladen. Dabei wird für die Klasse “Myproject_Model_Xyz” die Datei “APPLICATION_PATH . /’../einordner/models/xyz.php” eingebunden. Es können beliebig viele “Unter-Namespace” definiert werden, was für eigene Klassen wie Models, Forms oder Validatoren sinnvoll ist.

Weitere Informationen und Beispiele sind im Manual zu finden. Keith Pope beschreibt anhand eines Beispiels sehr schön, wie er Zend_Loader_Autoloader_Resource in seinem Projekt verwendet (siehe “Seite 129ff, Zend Framework 1.8 – Web Application Development” von Keith Pope).

Dieser Weg Klassen automatisch zu laden gefällt mir ganz gut, weil er einfach ist und keine spezielle Loader Klasse nötig ist (die z.B. mittels einer statischen Funktion eine Klasseninstanz liefert). Natürlich offenbaren die Namespaces, welche mit PHP 5.3 eingeführt wurden neue Möglichkeiten, die ich mir auch noch genauer ansehen möchte. Oder gibt es eine noch schönere und elegantere Lösung? Dann bitte unbedingt kommentieren!

Eine große Sache bei der Entwicklung von Webapplikationen ist das Thema Sicherheit. Verfolgt man verschiedene Nachrichtendienste zum Thema Sicherheit, dann stellt man sehr schnell fest, dass zahlreiche Applikationen immer wieder kritische Sicherheitslücken aufweisen. Besonders wenn es sich um OpenSource Software handelt stellt das ein Problem dar, da ein Angreifer den Programmcode einer möglichen Opferapplikation kennt.

Ein Blick in die Literatur offenbart einige grundlegende Bedrohungen, die immer wieder anzutreffen sind:

  • SQL Injection: fremde SQL Statements werden in die Opferapplikation eingeschleußt und von dieser ausgeführt
  • Command Injection: Einschleusen (Injizieren) bösartiger Befehle zur Kompromittierung der Funktionsschicht
  • Cross-Site Scripting: Einschleusen (Injizieren) von bösartigen Programmcode
  • Directory Traversal: Technik um an nicht öffentliche, aber zugängliche Daten zu kommen
  • Header Injection: Manipulation von dynamisch generierten Header
  • Session Hijacking: Technik zur Übernahme einer fremden Session
  • Session Fixation: Technik zur Session-Manipulation um Session des Angreifers zu privilegieren
  • Cookie Poisoning: Manipulation von Cookies

Eine sehr schöne Übersicht mit möglichen Gegenmaßnahmen zeigt hier das Buch “Pro PHP Security” von Chris Snyder und Michael Southwell auf (apress Verlag, ISBN: 1-59059-508-4).

PHPIDS

PHPIDS Logo

Vor einiger Zeit bin ich auf PHPIDS gestoßen, einem Intrusion Detection System für PHP. Dabei handelt es sich um ein Skript, das mittels regulären Ausdrücken und einem generischen Ansatz (Zentrifuge-Ansatz) die übergebenen Parameter (GET, POST, COOKIE, REQUEST) überprüft und mögliche Angriffe erkennt.

Ein erkannter Angriff wird mit einem “Impact” Wert gekennzeichnet, der angibt, wie schwerwiegend der Angriff ist. Ein Impactwert von 2-5 ist unbedenklich, wobei ein Impact 15 schon sehr hoch ist und eine Reaktion erfordert. Ein sehr hoher Wert (25-50) macht eine Reaktion unbedingt erforderlich. Dabei kommt PHPIDS auch mit exotischen Zeichensätze (UTF-7), JavaScript Unicode, dezimal und hexcode usw. zurecht und erkennt alle gängigen Angriffsmuster wie SQL Injection, Cross-Site Scripting Attacken oder Directory Traversal Zugriffe.

PHPIDS steht unter LGPL und kann unter http://php-ids.org frei heruntergeladen werden.

Arbeitsweise

PHPIDS besitzt eine Liste von Filterregeln. Diese definieren reguläre Ausdrücke, welche gängige Angriffmuster erkennen. Dazu wird der Angriff mit einem Tag und einem Impactwert gekennzeichnet. Greifen mehrere Filterregeln, dann werden die einzelnen Impactwerte aufkummuliert. Um neue, unbekannte Angriffe zu erkennen, wird ein Zentrifuge-Ansatz verwendet, d.h. ist ein Parameter ein String von der Mindestlänge von 40 Zeichen, dann wird dieser String durch geschickte Zeichenersetzung auf eine repräsentative, kurze Zeichenkombination reduziert. Hierbei werden:

  1. alle Wortzeichen sowie Whitespace (berücksichtigt Unicode) entfernt
  2. mehrfach vorkommende Zeichen werden gestrippt
  3. bestimmte Zeichengruppen durch festgelegte Zeichen ersetzt (Ziel: Anzahl unterschiedliche Zeichen gering halten)
  4. unerwünschte Zeichen (z.B. Backslash aus Magic-Quotes-Funktion) entfernt

Das Ergebnis ist ein String aus 4 bis 6 Zeichen. Cross Site oder Code Injection Angriffe führen dabei erstaunlicherweise immer wieder auf ein ähnliches Muster. Taucht ein solches Muster (z.B. “((+::”) auf, so schlägt PHPIDS Alarm.

Installation

Die Installation ist denkbar einfach. In der zentralen Bootstrap Datei einer Applikation werden die PHPIDS Klassen geladen, alle Parameter als Array übergeben und das Ergebnis ausgewertet. Die Reaktion kann selbst ausgestalltet werden und kann von einfachen Logging (PHPIDS bringt hier bereits eine Hilfsklasse mit) bis hin zum Sperren der IP Adresse reichen.

Einbinden und Starten von PHPIDS:

$request = array(
'REQUEST' => $_REQUEST,
'GET' => $_GET,
'POST' => $_POST,
'COOKIE' => $_COOKIE
);

require_once 'IDS/Init.php';
$init = IDS_Init::init('/IDS/Config/Config.ini.php');
$ids = new IDS_Monitor($request, $init);
$result = $ids->run();

// Angriff erkannt? Loggen und Abbruch
if (!$result->isEmpty()) {
	require_once 'IDS/Log/File.php';
	require_once 'IDS/Log/Composite.php';

	$compositeLog = new IDS_Log_Composite();
	$compositeLog->addLogger(IDS_Log_File::getInstance($init));
	$compositeLog->execute($result);

	die("ids error ".$result->getImpact());
}

Hat die eigene Applikation keine Bootstrap Datei, so kann mit Hilfe der PHP Option “auto_prepend_file” eine Datei angegeben werden, die immer vor der eigentlichen PHP Datei geparst wird. Diese kann unter Apache auch in der .htaccess gesetzt werden:

php_value auto_prepend_file phpids.php

phpids.php muss dann den oben stehenden Programmcode enthalten und PHPIDS ausführen.

Hat man beispielsweise einen Blog, wo tatsächlich HTML eingegeben und übertragen wird (z.B. in der Administrationsoberfläche beim Erstellen neuer Blogeinträge), so muss das PHPIDS hier bescheid wissen, da es sonst Alarm schlägt. Hierzu können in der Konfiguration Parameter festgelegt werden, die HTML enthalten dürfen. Ebenso schlägt PHPIDS bei der Verwendung von Google Analytics Alarm. Dieses ist zwar schon vorkonfiguriert, ich musste es aber immer nochmal für REQUEST und COOKIE konfigurieren:

exceptions[]    = REQUEST.__utmz
exceptions[]    = REQUEST.__utmc
exceptions[]    = COOKIE.__utmz
exceptions[]    = COOKIE.__utmc

Performance

Natürlich hat PHPIDS auch eine Auswirkung auf die Performance der Applikation. Hier haben die Entwickler verschiedene Szenarien getestet und es hat sich gezeigt, dass PHPIDS lediglich 0,5 % der gesamten Rechenzeit benötigt, wenn z.B. von einer CakePHP Applikation ausgegangen wird. Selbst wenn man davon ausgeht, dass die Zahlen geschönt sind, ergibt sich aus meiner Sicht hier trotzdem kein Problem. Nimmt man beispielsweise einen privaten Weblog, der auf WordPress basiert, dann dürften sich hier die Besucherzahlen in Grenzen halten und das IDS nicht ins Gewicht fallen.

PHPIDS berücksichtigt das Performanceproblem bereits und verzichtet darauf z.B. den Zentrifuge-Ansatz auf kurze Strings anzuwenden. Zudem beschränkt es sich auf Strings: Integer Werte werden beispielsweise nicht geprüft. Wer trotzdem noch optimieren will, dem bietet PHPIDS ein Caching Mechanismus, so dass die Liste mit den Filterregeln nicht jedes mal neu geparst werden muss. Zudem liegen die Filterlisten nicht nur als XML Datei vor, sondern auch im JSON Format, welches bedeutend schneller geparst wird.

Literatur und Links

  • c’t magazin 2009 Heft 10: Alarmanlage – Angriffe auf Webanwendungen mit PHPIDS erkennen, Christian Matthies, 27.04.2009
  • PHPIDS Webseite: http://php-ids.org/

Schon vor einiger Zeit bin ich auf eine sehr schöne Lösung gestoßen, mit der sich ein sicherer Login mit Einmalpassworte realisieren lässt. Dabei wird ein Hardware USB Dongle verwendet, welcher nach einem Druck auf einen Taster ein Einmalpasswort generiert. Die serverseitige Prüfung kann aus einer beliebigen Sprache heraus passieren. Für mich bietet sich hier natürlich PHP an. In meinem Fall verwende ich für die Applikation das Zend Framework, aber die hier vorgestellte Lösung ist allgemein verwendbar und unabhängig vom restlichen Backend.

Was ist ein Einmalpasswort?

Wie der Name bereits sagt, kann ein Einmalpasswort nur für einen Login verwendet werden. Dann erlischt die Gültigkeit und ein neues Einmalpasswort wird benötigt. Ein solches Passwort wird durch eine spezielle Hardware (hier OpenKubus Stick) erzeugt. Eine genaue Definition findet ihr auch in Wikipedia, wo auch verschiedene Algorithmen vorgestellt werden. Der englischsprachige Begriff ist One Time Passwort und hierzu findet man auch auf dem internationalen Wikipedia eine interessante Beschreibung.

Vorteile Einmalpasswörter

Zu Beginn stellt sich die Frage, welche Vorteile ein Login mit Einmalpasswörter mit sich bringt. Hier macht es Sinn, sich der Problemstellung von Seite der Bedrohungen aus zu nähern und zu analysieren, ob ein solches System für den eigenen Anwendungsfall eine sinnvolle Maßnahme ist oder nicht. Mögliche Bedrohungen, gegen die ein Einmalpasswort eine risikominimierende Wirkung hat sind:

  • an erster Stelle natürlich eine sichere Authentifikation: nur wer den Stick besitzt kann sich auch in das System einloggen
  • ungeeigneter Umgang mit Passwörter: es kann nicht sichergestellt werden, dass Benutzer unsichere Passwörter verwenden
  • Abhören von Leitungen/Ausspähen des Passwortes: es kann passieren, dass in einer unsicheren Umgebung die eigenen Zugangsdaten abgehört werden. Hier wäre auch eine verschlüsselte Verbindung eine empfehlenswerte Gegenmaßnahme, aber auch ein Einmalpasswort bietet hier einen geeigneten Schutz, da dieses nur für einen Login gültig ist.
  • systematisches Ausprobieren von Passwörter: es kann passieren, dass ein Brute Force Angriff auf das System durchgeführt wird. Ein Einmalpasswort ist in der Regel sehr lange und somit schwerer zu knacken
  • Trojanische Pferde: am Clientrechner könnte ein Schadprogramm installiert sein, welches die Zugangsdaten ausspäht

Natürlich ist ein Einmalpasswort kein Allheilmittel. So schützt es nicht vor Pishing Angriffen, oder Man in the Middle Attacken, wo die Logindaten abgefangen und erst garnicht an den Server gesendet werden.

Komponenten

An dieser Stelle will ich zuerst die verschiedenen Komponenten vorstellen, die für die Lösung verwendet wurden. Natürlich ist alles OpenSource und frei verfügbar. Auch der Hardwaredongle ist frei und recht günstig zu erstehen.

OpenKubus

OpenKubus

Der OpenKubus ist ein kleiner, handlicher USB Stick (der Problemlos an den Schlüsselbund passt), dessen Hardwarelayout frei verfügbar ist und der frei programmiert werden kann. Wird der Stick an einem Rechner angesteckt, so wird er als Tastatur erkannt. Ein Druck auf einen Taster führt dazu, dass der Stick ein neues Einmalpasswort generiert und als Tastatureingabe an den Rechner sendet. Es reicht also, den Cursor auf das Eingabefeld im Login Formular zu setzen und der Stick schießt sein Passwort hinein. Dabei ist der Stick kompatibel zu nahezu allen Systemen, weil er als einfache Tastatur arbeitet.

Der Stick kostet 24,95 Euro und kann im embedded projects Shop bestellt werden. Beispielprogramme und die nötige Software um den Stick zu programmieren (was unter Linux recht leicht möglich ist), sind auf der google code Projektseite zu finden. Dort gibt es auch Anleitungen und ein Beispielserver in Perl. Der OpenKubus kann auch für einen sicheren Linux Login (PAM) verwendet werden.

phpseclib

phpseclib

Um das Passwort zu entschlüsseln (AES), benötigt man eine passende Bibliothek. phpseclib bietet eine komfortable und objektorientierte Lösung, die auf keine PHP Extensions oder ähnliches angewiesen ist. Diese Bibliothek ist in PHP geschrieben, entwickelt man in einer anderen Sprache, so benötigt man irgendwie eine Möglichkeit mit AES Verschlüsselung zu arbeiten.

phpseclib ist aus meiner Sicht für Verschlüsselung allgemein ein Tipp. Es stehen Klassen für die verschiedensten Algorithmen zur Verfügung (z.B. RSA, SSH, DES, 3DES, AES uvm.). Die Bibliothek ist OpenSource und steht unter der LGPL Lizenz.

Ablauf

Um die unten stehende Implementierung zu verstehen, muss man zuerst den gesamten Ablauf kennen. Dieser ist recht simpel. Zuerst muss der Stick einmalig vorbereitet werden. Der Authentifizierungsprozess kann dann beliebig oft durchgeführt werden.

Initialisierung

Der Stick muss mit dem AES Schlüssel und einem zufälligen Datenblock beschrieben werden. Dies funktioniert recht einfach unter Linux:

sudo ./stick-write -p AESKeyundDatenblock

Wobei AESKeyundDatenblock ein 46 Byte langer String ist, der zuerst aus dem AES Key (32 Bytes lang) und dann dem Datenblock (14 Bytes lang) besteht. Der AES Schlüssel und der Datenblock müssen auch dem Server bekannt sein. Zudem muss auf dem Server der Zähler auf 0 gesetzt werden (=das nächste Einmalpasswort ist das erste).

Authentifizierung

Client:

  1. Der Stick wird am Client eingesteckt und das Loginformular wird aufgerufen. Nun gibt der Benutzer seinen Benutzernamen ein, setzt den Cursor auf das Einmalpasswort-Feld und drückt auf den Taster am Stick.
  2. Der Stick erzeugt nun das Einmalpasswort: Ein interner Zählwert wird mit dem Datenblock verknüpft und mit dem angegebenen Schlüssel (key) AES verschlüsselt. Anschließend erhöht der Stick seinen internen Zähler um eins.
  3. Das so generierte Einmalpasswort wird base64 kodiert, so dass es durch gewöhnliche (ASCII) Zeichen darstellbar ist.
  4. Der Stick sendet das so generierte, base64 kodierte Einmalpasswort als Tasteneingabe und befüllt so das selektierte Feld. Ein Klick auf “Login” sendet das HTML Formular wie bei einem gewöhnlichen Login an den Server.

Server:

  1. Der Server nimmt die Anfrage entgegen und lädt seinen eigenen, zum Stick passenden AES Schlüssel und Datenblock.
  2. Dann wird der durch den Client übermittelte base64 String erst einmal korrigiert: ein z am Anfang kennzeichnet ob ein amerikanisches Tastaturlayout vorliegt (entsprechend müssen alle z durch y und alle y durch z ersetzt werden). Zudem werden einige Sonderzeichen ersetzt.
  3. Anschließend wird das noch verschlüsselte Einmalpasswort von base64 wieder in einen Bytevektor dekodiert.
  4. Nun wird das Einmalpasswort mit AES entschlüsselt.
  5. der Zählstand wird ausgelesen und der Datenblock entnommen.
  6. Der Login ist erfolgreich wenn der gegebene Zählstand größer dem Zählstand am Server ist und der gegebene Datenblock mit dem intern gespeicherten übereinstimmt.
  7. War der Login erfolgreich, so wird am Server der Zählstand des Sticks übernommen: es werden nur Einmalpasswörter akzeptiert, die einen höheren Zählstand haben (alte funktionieren also nicht mehr).

Auch wenn sich alles nun nach einer Menge Arbeit anhört, ist die Implementierung übersichtlich. Ich habe dazu in meiner Applikation einen Action Helper verwendet (in Zend Framework Applikationen eine Hilfsklasse, die in allen Controller zur Verfügung steht). An dieser Stelle eine PHP Funktion, welche nur von phpseclib abhängt und alle Schritte durchführt, die oben unter Server gelistet sind. Jeder Schritt wird nochmal als Kommentar erklärt.


/**
 * @param string $givenOtp Einmalpasswort, dass geprueft werden soll
 * @param string $aesKey der AES Schluessel und Datenteil
 * @param int $number aktueller Zaehlstand des Servers
 */
function otp($givenOtp, $aesKey, $number) {
	// AES Schluessel besteht aus
	// erste 32 Byte: AES Key
	// restliche 14 Byte: Datenblock
	$key  = substr($aesKey, 0, 32);
	$data = substr($aesKey, 32, 14);

	// base64 Kodierung des Sticks korrigieren
	$givenOtp = rtrim($givenOtp);

	// Sonderzeichen in korrekte Sonderzeichen umwandeln
	// (diese werden vom Stick anders vorgegeben, um von
	// unterschiedl. Tastaturlayouts unabhängig zu werden)
	for($i = 0; $i < strlen($givenOtp); $i++) {
		if($givenOtp[$i] == " ") { $givenOtp[$i] = "/"; }
		elseif($givenOtp[$i] == ".") { $givenOtp[$i] = "="; }
		elseif($givenOtp[$i] == "-") { $givenOtp[$i] = "+"; }
	}

	// erstes Zeichen pruefen ob z oder y
	// abhaengig davon alle y durch z ersetzen und umgekehrt
	$z = $givenOtp[0];
	$crypted = substr($givenOtp, 1);

	if($z == "y" or $z == "Y") {
		for($i = 0; $i < strlen($crypted); $i++) {
		  if	($crypted[$i] == 'y') { $crypted[$i] = "z"; }
		  elseif($crypted[$i] == 'Y') { $crypted[$i] = "Z"; }
		  elseif($crypted[$i] == 'z') { $crypted[$i] = "y"; }
		  elseif($crypted[$i] == 'Z') { $crypted[$i] = "Y"; }
		}
	}

	if($z == "Y" or $z == "Z") {
		for($i = 0; $i < strlen($crypted); $i++) {
			if(ctype_upper($crypted[$i])) {
				$crypted[$i] = strtolower($crypted[$i]);
			} else {
				$crypted[$i] = strtoupper($crypted[$i]);
			}
		}
	}

	// base64 String nun in binary dekodieren
	$crypted = base64_decode($crypted);

	// gegebenes One Time Passwort mit AES entschluesseln
	$aes = new Crypt_AES();
	$aes->disablePadding();
	$aes->setKey($key);
	$plain = $aes->decrypt($crypted);

	// entschluesseltes Passwort aufteilen in

	// aktueller Zaehlstand
	$i	 = substr($plain, 0,1);
	$j	 = substr($plain, 1,1);
	$n	 = ord($i) + (ord($j) << 8);

	// und Datenblock
	$plain = substr($plain, 2, strlen($plain)-2);

	// wenn Datenblock korrekt und Laufnummer nach aktueller Nummer:
	// aktuelle Nummer zurueckgeben (Login korrekt)
	if(($plain == $data) and ($n > $number)) {
		return $n;
	} else {
		return false;
	}
}

Natürlich kann das Entschlüsseln auch mit der mcrypt Erweiterung von PHP durchgeführt werden. So benötigt man keine zusätzliche Bibliothek wie phpseclib. Um das Passwort mit mcrypt zu entschlüsseln, muss der Codeteil


$aes = new Crypt_AES();
$aes->disablePadding();
$aes->setKey($key);
$plain = $aes->decrypt($crypted);

durch die entsprechenden mcrypt Aufrufe ersetzt werden:


$td = mcrypt_module_open("rijndael-128", "", "ecb", "");
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_DEV_RANDOM);
mcrypt_generic_init($td, $key, $iv);
$plain = mdecrypt_generic($td, $crypted);

Fazit

Ich bin mit der Lösung sehr zufrieden. Die AES Schlüssel/Datenblöcke lassen sich komfortabel in der Datenbank ablegen und auch leicht ändern, falls der Stick verloren geht. Besonders wenn man oft in unsicheren Netzen unterwegs ist (z.B. in der Firma oder an der Uni, in Internet Cafes usw.) ist die Lösung praktisch. Es müssen keine langen Einmalpasswörter von einem Display abgeschrieben werden und der Stick läuft gleichermaßen unter Linux und Windows. Die Integration in die eigene Applikation ist denkbar einfach.

Einziger Nachteil: der Stick hat keine Zeitkomponente, d.h. jemand könnte das Passwort abgreifen und dann die Netzverbindung kappen. Erst wenn sich der Besitzer einloggt, wird das zuvor abgepishte Passwort ungültig. Ebenso lassen sich mehrere Passwörter auslesen: so lange der Besitzer sich nicht einloggt, bleiben sie alle gültig.