[php] Oracle XE und Doctrine/Symfony funktioniert nicht

Kurzer Warnhinweis: ich bin kein Freund von Oracle und finde es zumindest im Bezug auf php relativ kompliziert in der Einrichtung. Dieser Artikel besteht daher zum Teil aus gefährlichem Halbwissen – falls ich also Blödsinn erzähle, korrigiert mich bitte per Kommentar oder Mail 😉

Wenn ihr ein Symfony 2 Projekt mit einer Oracle XE Datenbank verbindet und dann folgende Fehler auftauchen – z.B. bei einem app/console doctrine:schema:create :

[Doctrine\ORM\Tools\ToolsException]
Schema-Tool failed with Error '' while executing DDL: CREATE TABLE Foo (id NUMBER(10) NOT NULL, name VARCHAR2(100) NOT NULL, PRIMARY KEY(id))
[Doctrine\DBAL\Driver\OCI8\OCI8Exception]

Dann müsst ihr folgende Dinge richtig machen:

Eure Konfiguration sollte so aussehen:

parameters:
    database_driver:   oci8
    database_host:     localhost
    database_port:     1521
    database_name:     xe
    database_user:     dbuser
    database_password: dbpass

Beim Treiber könnte man auch pdo_oci verwenden – was auch in vielen Tutorials empfohlen wird. Das Problem ist jedoch, dass dieser Treiber sehr instabil ist. Daher einfach oci8 verwenden 😉

Hostname und Port sollten klar sein, der Datenbankname ist bei einem Oracle XE grundsätzlich “xe”. Was ihr dann innerhalb eures Oracle Clients seht, was sich ähnlich wie eine DB in MySQL anfühlt, ist in der Oracle Sprachwelt ein Schema. Das Schema ist in der Regel gleich dem DB-Nutzernamen.

So, dass zu den Einstellungen. Der fiese Part, durch den es zum oben genannten Fehler kommen kann, ist, dass man noch zwei Umgebungsvariablen auf der Shell und im Apachen setzen muss, damit die Verbindung von Symfony zu Oracle korrekt läuft:

export ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe
export LD_LIBRARY_PATH=/usr/local/lib

Diese beiden Zeilen (natürlich mit den jeweils korrekten Pfaden für euer System) packt ihr entweder in die .bashrc des jeweiligen Users oder in die envvars Datei vom Apachen (diese Angaben beziehen sich primär auf ein Standard Debian, wie es in anderen Distributionen aussieht weiß ich nicht!).

Falls ihr die Variablen für den Apachen setzen müsst, nicht vergessen, diesen mittels Neustart dazu zu bewegen, diese auch zu laden 😉

Nun sollte die Verbindung laufen.

Wie speichert man Passwörter

Über den Beitrag Passwortmythen oder „Was Du schon immer über Passwörter wusstest, aber nie zu sagen wagtest“ bin ich auf das Thema Passwortsicherheit bei Web-Applikationen gestoßen und habe mir ein paar Gedanken dazu gemacht.

Für alle technisch nicht so versierten Leser hier mal ein kurzer Abriss, wie die derzeitigen Möglichkeiten für die Passwort-Speicherung in Benutzertabellen aussieht: man kann (aber sollte dies NIEMALS tun) die Passwörter im Klartext abspeichern, man kann sie hashen, mit einem Salt hashen und mit per User Salts hashen. Dabei gehe ich natürlich die ganze Zeit aus Sicht der Web-Applikation vor.

Klartextpasswörter

Die Klartextspeicherung sollte klar sein: man speichert das übertragene Passwort einfach so, wie man es empfängt, in der Datenbank. Wenn der User sich im System anmeldet, dann gibt er seinen Benutzernamen und sein Passwort ein. Wenn man die Daten empfangen hat, zieht man sich den Datensatz dieses Benutzers aus der Datenbank und vergleicht das übertragene Passwort mit dem in dem Datensatz gespeicherten. Wenn beide übereinstimmen, kann man den User als eingeloggt markieren. An sich ist dieses Vorgehen nicht problematisch und von außen her sicher. ABER: wenn irgendjemand – über welchen Weg auch immer – Zugriff auf die Benutzertabelle bekommt, dann kann er die Passwörter direkt lesen. Auch das wäre noch nicht SO kritisch, wenn alle User verantwortungsvoll mit ihren Login-Daten umgehen würden. Das tun sie aber nicht. Denn: ein sehr großer Teil aller Computerbenutzer verwendet EIN Passwort für ALLE Logins – also Facebook, Ebay, Email, Online Games, usw. D.h., wenn man nun die Passwörter eines Systems herausfindet, dann ist es sehr wahrscheinlich, dass man sich bei der angegebenen Email-Adresse mit dem gleichen Passwort anmelden kann. Von dort aus kann man dann sehr leicht auf alle anderen Accounts des Benutzers zugreifen, da man ja nun die Passwörter zurück setzen kann und die entsprechenden Mails empfängt.

Hashes

Ok, dann müssen wir die Passwörter also verschlüsseln. Wichtig ist dabei, dass man dies mit einer Einweg-Berechnung erledigt – denn sonst könnte man den berechneten Wert einfach wieder zurück rechnen. Für diese Aufgabe gibt es die Hash-Funktionen. Diese Einwegfunktionen erzeugen aus einer beliebig langen Zeichenfolge einen 32-stelligen Text aus Zahlen und Buchstaben. Beispiele für solche Funktionen sind MD5 oder SHA-1. Der Vorteil ist: man kann diese Werte nicht in die Ursprungswerte zurück rechnen und man kann auch keine Rückschlüsse auf den ursprünglichen Wert schließen. Um das zu verdeutlichen, hier mal die MD5 Hashes für die Zahlen 0, 1, 2:

md5(0) = cfcd208495d565ef66e7dff9f98764da
md5(1) = c4ca4238a0b923820dcc509a6f75849b
md5(2) = c81e728d9d4c2f636f067f89cc14862c

Wie man sieht: völlig unterschiedliche Werte. Ok, wir haben nun also die Möglichkeit, Passwörter nicht rekonstruierbar abzuspeichern. Aber eines haben wir nicht beachtet: die Freaks. Nach einiger Zeit kamen ein paar Verrückte auf die Idee, einfach alle möglichen Zeichenkombinationen zu nehmen, die entsprechenden Hashes zu erzeugen und diese dann in einer Datenbank abzuspeichern. Hat man nun einen Hash aus einer Benutzertabelle, fragt man einfach diese Datenbank ab und erhält das entsprechende Klartext-Passwort. Außerdem ist anhand der Hashes sehr schnell erkennbar, wenn unterschiedliche Benutzer das gleiche Passwort verwenden – denn dann steht bei diesen jeweils der gleiche Hash in der Datenbank.

Rainbow Tables

Die Sache hat aber den Haken, dass bereits bei einem Wort mit 6 Zeichen der Typen [A-Za-z0-9./] über 1,4 Tb an Daten zusammenkommen. Also hat man die sogn. Rainbow-Tables entwickelt, die zwar genauso vorgehen, aber nur einen Teil des Hashes in der Datenbank abspeichern (wird durch Reduktionsfunktionen verkürzt). Dadurch reduziert sich die Menge der Daten deutlich. Mittels der Rainbow Tables kann man also mit einem gegebenen Hash sehr schnell auf das ursprüngliche Passwort kommen.

Salted Hashes

Da wir nun wissen, dass unsere in der Datenbank abgespeicherten, gehashten Passwörter zwar auf den ersten Blick nicht direkt lesbar sind, aber mittels Rainbow Tables innerhalb kürzester Zeit sehr schnell zum Vorschein kommen, verwenden wir einen einfachen Trick, um den Hash doch wieder einzigartig zu machen: wir salzen ihn. Man nimmt also einfach das zu verschlüsselnde Passwort und setzt eine beliebige Zeichenfolge davor oder hinten dran – oder beides, und bildet davon den Hash. Beispiel:

Unser Passwort lautet = Passwort
Unser Salt lautet = s3cureIt
Hash: md5(Passwort) = 3e45af4ca27ea2b03fc6183af40ea112
salted Hash: md5(s3cureItPasswort) = 81fc33ece07aa1357d469ec36f5e2e55

Wie ihr seht, kommen 2 völlig unterschiedliche Hashes heraus, obwohl wir in beiden Fällen das gleiche Passwort verschlüsselt haben. Der Vorteil an diesem Verfahren ist, dass man nun den Hash nicht mehr ohne weiteres über eine vor-generierte Datenbank abfragen kann, da das Passwort, welches verschlüsselt wurde, ja nicht “Passwort”, sondern “s3cureItPasswort” lautet. Man hat durch das Salt so gesehen ein viel längeres Passwort, und außerdem kann man auf diesem Wege nun doch Sonderzeichen einbauen, die der Benutzer in seinem Passwort so wohl nicht verwendet. Der Punkt ist: wenn jemand die Benutzertabelle entwenden kann, dann kommt er auch an den verwendeten Salt-Text heran. Was aber nicht weiter schlimm ist, denn auch mit dem Salt braucht der “Einbrecher” sehr sehr lange, um die Passwörter zu entschlüsseln.

Wenn man nun eine Benutzertabelle mit 1 Million Einträgen erhält, dann kann man durchaus die Zeit investieren und einfach Rainbow-Tables erzeugen, die eben alle möglichen Wörter mit dem gegebenen Salt verwenden. Man baut also speziell für diese Tabelle eine eigene Datenbank auf. So steckt man vielleicht den Rechenaufwand von ein paar Monaten hinein, hat dann aber schlagartig Zugriff auf 1 Million Benutzernamen und Passwörter.

per User salted Hashes
Um auch diese Problematik zu umgehen, hat man die Salt-Variante noch etwas abgewandelt und geht nun so vor: Für jeden Benutzer wird ein eigener Salt erzeugt und in dem Datensatz des Benutzers gespeichert. Wenn er sich nun einloggt, wird der Datensatz des Benutzers gelesen, das eingegebene Passwort mit dem gespeicherten Salt kombiniert und erst dann der Hash gebildet. Anschließend vergleicht man dieses Ergebnis wieder mit dem aus der Datenbank und bei Übereinstimmung kann man den Benutzer einloggen.

Dieser kleine Umweg führt aber dazu, dass, selbst wenn man über eine komplette Benutzertabelle mit den Hashes und den Salts verfügt, man pro Benutzer mehrere Monate benötigt, um das jeweilige Klartextpasswort herauszufinden.

Zusammenfassung
Der kleine Abriss ist nun doch etwas umfangreicher geworden, zeigt aber grob die Vorgehensweisen. Ich möchte hier anmerken, dass das nur die stark gekürzte Variante für Laien ist – wer sich umfangreicher mit den Themen beschäftigen möchte, sollte die Begriffe einfach mal bei Wikipedia nachlesen.

Bisher setze ich auf (per user) salted Hashes, die meiner Meinung nach eine relativ hohe Sicherheit bieten – solange sichere Passwörter verwendet werden. Da man bei Usern aber nicht davon ausgehen kann, dass sie sichere Passwörter wie “#,hgfsdU4358$%&§jkdfg8734ä#++..-” verwenden, ist die Sicherheit natürlich eingeschränkt. Leider verwenden viele Benutzer noch immer Passwörter wie “Sommer”, “Gott” oder “Erde”, welche sehr sehr schnell zu knacken sind. Wenn solche Passwörter verwendet werden, helfen auch die ausgeklügeltesten Sicherungsverfahren nicht viel weiter, da die Berechnungszeit der Passwörter dadurch auf wenige Sekunden verkürzt werden.

Als Fazit für alle Internetnutzer kann ich daher ganz klar sagen: VERWENDET NIE DAS GLEICHE PASSWORT FÜR ALLE EURE ZUGÄNGE!

Wie mache ich es besser?

Das große Problem, mit welchem im Prinzip jede Verschlüsselung zu kämpfen hat, ist Zeit. Wie viel Zeit brauche ich, um ein Passwort zu knacken? Da Rechner mit jedem Jahr schneller und schneller werden und nun auch sehr performante Chips wie GPUs zum Einsatz kommen, sind viele Verschlüsselungen nutzlos geworden – man kann sie einfach innerhalb kürzester Zeit knacken.

Und somit kommen wir auf den eigentlichen Inhalt dieses Artikels: bcrypt. Das Tool setzt nämlich genau bei diesem Problem an: man kann die Zeit, die man benötigt, um einen Hash zu erzeugen, beeinflussen. Das hat den großen Vorteil, dass man die Verschlüsselung an die Geschwindigkeit moderner Rechner anpassen kann und diese somit nicht automatisch in ein paar Jahren unbrauchbar wird. Das restliche Handling ist gleich – man übergibt einen Wert plus eben einen Zeitfaktor und erhält danach einen Hash-Wert. Da man nun die Berechnungszeit beeinflussen kann, dauert es eben nicht mehr eine Mikrosekunde, um einen Hash zu berechnen, sondern 0,5 Sekunden. Wenn dieser Fall eintritt, dann ist es einfach nicht mehr möglich, innerhalb einer sinnvollen Zeitspanne gegen diese Verschlüsselung mit Bruteforce (also dem Durchprobieren aller möglichen Zeichenkombinationen) oder großen Wordlisten vorzugehen – egal wie schnell der Rechner ist.

Gerade für php Entwickler ist dies eine sehr einfache Möglichkeit, die Benutzertabellen ihrer Applikationen abzusichern – denn bcrypt ist seit php 5.3 im Basissystem enthalten: crypt.

Ich hoffe, dass ich nichts vergessen habe – andernfalls freue ich mich natürlich über Meinungen in den Kommentaren…

[Quicktip] MySQL Query-Ergebnis per Console in eine CSV Datei exportieren

Mit phpmyadmin und Konsorten ist es kein Problem, Ergebnisse von SQL Abfragen in CSV-Dateien zu exportieren. Problematisch wird es allerdings, wenn man eine aufwändigere Abfrage hat, die den php Timeout provoziert, oder aber man keinen Web-Client zu Verfügung hat. Dann greift man zur Konsolenversion von MySQL – welche diesen Export nicht direkt anbietet. Mittels ein bisschen Bash-Magie kann man sich aber behelfen:

mysql --database=database --execute="select a from b where a>1;" | sed 's/\t/","/g;s/^/"/;s/$/"/;s/\n//g' > filename.csv

Was passiert? Die Abfrage wird ausgeführt und die Ergebnisse werden in Textform ausgegeben. Mittels Pipe wird die Ausgabe an “sed” weitergegeben, welches diese in das CSV Format umwandelt und anschließend in die Datei “filename.csv” schreibt.

Via snipplr.com