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.

Eine einfache und ansprechende Möglichkeit mehrere Informationen auf Webseiten zu präsentieren, bietet ein “carousel” Effekt. Besonders wenn eine Liste von Eigenschaften veranschaulicht, Screenshots oder Features oder Fotografien präsentiert werden sollen, bietet es sich an, die einzelnen Bilder nacheinander einzuschieben und wieder verschwinden zu lassen (Beispiel: rsslounge). Neben einer sehr guten Nutzung des zur Verfügung stehenden Platzes, fällt durch die Bewegung die Information auf und lenkt die Aufmerksamkeit auf wichtige Kernbotschaften.

Slide Effekte und Carousel Skripte gibt es im Internet viele, überzeugen konnte mich keines. Meistens sind sie zu umfangreich oder es ist spezielles CSS notwendig, das dann unüberschaubar eingebunden bzw. die eigene Struktur daran angepasst werden muss. Nachdem jQuery sehr komfortabel ist und ich die meisten meiner Projekte auf jQuery aufbauen, habe ich mich entschlossen selbst ein einfaches jQuery Plugin zu schreiben. Dieses kommt ohne spezielles CSS aus und ist mit 1,8 kB auch schön kompakt.

Das Skript erwartet folgende HTML Struktur:

<ul id="carousel">
 <li>erster Frame (z.B. ein Bild)</li>
 <li>zweiter Frame (z.B. ein Bild)</li>
 <li>dritter Frame (z.B. ein Bild)</li>
 <li>vierter Frame (z.B. ein Bild)</li>
 ...
</ul>

Um nun aus der Liste ein carousel zu machen, reicht beispielsweise folgender Aufruf aus:

$("#carousel").simplecarousel({
 slidespeed: 700,
 auto: 5000,
 width: 480,
 height: 280
});

In obigen Fall ist das carousel 480×280 Pixel groß, wechselt automatisch alle 5 Sekunden zum nächsten Frame und der Wechsel (Geschwindigkeit des slide Effektes) dauert 0,7 Sekunden.

Es werden folgende Parameter unterstützt:

  • width: (int) Breite
  • height: (int) Höhe
  • next: (jQuery Objekt/Selector) wird auf dieses Objekt geklickt, wird der nächste Frame aufgerufen
  • prev: (jQuery Objekt/Selector) wird auf dieses Objekt geklickt, wird der vorhergehende Frame aufgerufen
  • vertical: (boolean) true = vertikales carousel; false = horizontales carousel
  • auto: (int/boolean) false = kein automatischer Wechsel; Wert in Millisekunden = pause bis automatisch zum nächsten Frame gewechselt wird
  • slidespeed: (int) Geschwindigkeit des Wechsels in Millisekunden
  • visible: (int) Anzahl an Frames die gleichzeitig sichtbar sein sollen
  • fade: (int) wird dieser Wert angegeben, so wird anstatt zu sliden das Frame ausgeblendet und das neue wieder eingeblendet. Die Geschwindigkeit des fade Effektes ist dann dieser Wert (siehe aditu.de)

Demo:

Download:

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.

Erweiterbarkeit ist eine zentrale Anforderung an nahezu jede Applikation. Dabei stellt man relativ schnell fest, dass dies auf verschiedenen Ebenen einer Anwendung passieren kann. Auf einer elementaren Ebene spiegelt sich die Erweiterbarkeit in der grundlegenden Architektur wieder, kann aber auch sehr hoch angesiedelt werden. Beispielsweise in einer Pluginschnittstelle für externe Programmierer.

Ralf beschreibt in seinem Buch eine gute Möglichkeit eine auf dem Zend Framework basierende Applikation modular zu strukturieren (mit Teilmodule, die nach belieben eingefügt oder ausgehängt werden können). Häufig ist aber auch das zu weit gefasst und eine viel klarere und schmalere Schnittstelle reicht vollkommen aus. Gesucht ist also ein Mechanismus, der Teilfunktionen dynamisch nachlädt und in die Gesamtapplikation einbindet.

Ein Beispiel: mein webbasierter RSS Reader rsslounge kann verschiedene Quellen verfolgen und verarbeiten. So liest er gewöhnliche RSS Feeds. Aber weiterführend kann er auch RSS Feeds als Quelle für Bilder verarbeiten (und extrahiert aus diesen RSS Feeds nur die Bilder). Möglich ist aber auch deviantArt als Datenquelle. Sprich jede mögliche Datenquelle ist denkbar. Nicht nur RSS Feeds, sondern auch normale HTML Seiten (die in irgend einer Weise geparst werden) oder auch der Zugriff auf einen Email Account wäre denkbar. Es macht also Sinn hier eine Abstraktionsebene einzubauen und die Möglichkeit zu schaffen einfach neue Datenquellen hinzuzufügen.

Die Vorgehensweise, welche ich für rsslounge gewählt habe ist simpel: jede Datenquelle ist eine Klasse, welche eine fest vorgegebene abstrakte Basisklasse (rsslounge_source) implementieren muss. Diese abstrakte Basisklasse gibt die Methoden vor, die unbedingt angeboten werden müssen (z.B. load() um die Datenquelle abzurufen).

abstract class rsslounge_source {
	abstract public function load($url);
	abstract public function getHtmlUrl();
	abstract public function getId();
	//...
}

Alle Datenquellen, welche die abstrakte Basisklasse implementieren, werden in einem festen Verzeichnis abgelegt. Die Gesamtapplikation durchsucht diesen Pfad nach verfügbare Datenquellen, prüft, ob sie die nötige Basisklasse implementieren und verwendet sie dann im Programm. Soll beispielsweise ein neues Feed hinzugefügt werden, so werden alle verfügbaren Datenquellen gesucht und im Dialog gelistet:

Feed hinzufügen

Angenommen ich entschließe mich ein Jahr später eine neue Datenquelle für die API von Google Analytics zu realisieren, so muss ich lediglich eine neue Klasse von rssloung_source ableiten, die entsprechenden Methoden implementieren und diese dann in das Verzeichnis ablegen.

Dieses Prinzip wird auch Hollywood Prinzip genannt: “Don’t call us, we call you”. Die Datenquelle Klasse bietet Methoden an, welche von der Gesamtapplikation im Bedarfsfall aufgerufen werden. Das Plugin muss von der Gesamtapplikation nichts wissen und ausschließlich ihre eigene Funktionalität und damit die Schnittstelle realisieren.

Die Implementierung basiert auf den Reflection Mechanismus der seit PHP 5 zur Verfügung steht. Mit Hilfe der Reflection Klassen können zur Laufzeit Klassen geladen und deren Eigenschaften ausgelesen werden. Zudem kann über diesen Weg die Klasse dann auch instanziiert werden:

$class = new ReflectionClass("plugin_rss_feed");
$baseClass = new ReflectionClass("rsslounge_source");
$class->isSubclassOf($baseClass); // true wenn plugin_rss_feed die Klasse rsslounge_source implementiert
$obj = $class->newInstance();

Sollen, wie im obigen Beispiel, alle verfügbaren Datenquellen gelistet werden, so wird das Verzeichnis mit den Datenquellen nach PHP Dateien durchsucht, geprüft ob diese von rsslounge_source abgeleitet sind und falls dies zutrifft instanziiert. Alle Objekte der so gefundenen Klassen werden in ein Array gespeichert und dann für die weitere Verarbeitung an die Gesamtapplikation übergeben (die diese Klassen dann auflistet):

$pluginLocation = "/plugins";
$pluginPrefix = "plugins_";
$plugins = array();
foreach(scandir($pluginLocation) as $file) {
	if(is_file($pluginLocation . "/" . $file)
	&& substr($file,0,1)!="."
	&& strpos($file,".php")!==false) {

		$classname = str_replace(".php","",$file);
		$class = new ReflectionClass($pluginPrefix.$classname);

		if($class->isSubclassOf(new ReflectionClass("base_plugin")))
			$plugins[$pluginPrefix.$classname] = $class->newInstance();
	}
}

Natürlich macht es hier Sinn einen Cache einzubauen, da sich die verfügbaren Klassen nicht oft ändern. Zend_Cache bietet hier eine einfache Möglichkeit so ein Array dateibasiert oder mittels memcached zu cachen.

Für meinen RSS Reader habe ich diesen Mechanismus zum Laden der Datenquellen in einen Action Helper ausgelagert. So steht diese Funktionalität in der gesamten Applikation zur Verfügung. Wer an Details interessiert ist, sei an dieser Stelle auf Google Code verwiesen, wo der Quellcode von rsslounge zu finden ist.