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…

Was macht einen guten Programmierer aus?

Quelle: Geek&Poke

Nach nun knapp 6 Jahren in der Programmierung und mehreren kleinen und großen Projekten, habe auch ich mir ein Bild von der Welt der Softwareentwicklung machen können. Ich konnte einige Charaktäre kennenlernen und für mich haben sich bestimmte Punkte herauskristalisiert, welche meiner Meinung nach einen guten Entwickler ausmachen.

Beginnen wir erstmal mit dem handwerklichem Teil, den wohl jeder Entwickler drauf haben sollte. Neben Syntax und Semantik gehören auch Kenntnisse der Wirkungsweise von Programmiersprachen, Netzwerktechnik, Computertechnik im Allgemeinen, Design Patterns, Datenbanken, Betriebssystemeigenheiten usw. zum Erfahrungsschatz. Erst wenn man das komplexe Zusammenspiel dieser Komponenten versteht, so kann man auch alle Probleme, die da so kommen mögen, lösen.

Ein anderer Punkt, und den finde ich besonders wichtig, ist die Erfahrung. Entwickler, die eine Programmiersprache beherschen, können zwar schnell neue Sprachen erlernen, weil sie sich fast alle ähneln oder zumindest die gleichen Prinzipien verfolgen (hier gibt es natürlich Ausnahmen). Aber auch wenn der Entwickler die neue Sprache erlernt hat, so kennt er bestimmte Eigenheiten eben erst, wenn er auch eine gewisse Praxis hat (min. 0,5 – 1 Jahr). Das hat Auswirkungen auf die Geschwindigkeit des entwickelten Codes und natürlich auch auf die Produktivität des Entwicklers. Meiner Meinung nach ein häufig nicht bedachtes Problem.

Ein wichtiges Kriterium für einen guten Entwickler ist seine Arbeitsweise. Man kann streng nach Lehrbuch programmieren, man kann aber auch lösungsorientiert an ein Problem herangehen. Obwohl ich mich möglichst an die Konventionen halte, so bin ich doch auch öfter mal der lösungsorientierte Typ. Ich denke einfach, das man immer eine gute Balance zwischen Lesbarkeit des Codes und aber auch dessen Performance halten sollte. Natürlich macht es Sinn, streng objektorientiert zu entwickeln, an manchen Stellen kann dies aber sowohl in der Lesbarkeit als auch in der Geschwindigkeit des Codes negative Auswirkungen haben. Es ist aber von Fall zu Fall unterschiedlich – und die Grenze zum Gefrickel ist hauchdünn.

Quelle: Geek&Poke

Neben der Art und Weise, wie man seinen Code strickt, hat natürlich auch die Verwendung von Kommentaren und geeigneten Funktions/Variablennamen einen sehr großen Einfluss auf die Lesbarkeit. Meine Devise: lieber eine Funktion “getLoggedInUserData()” nennen als “getData()” – sagt beides das gleiche aus, jedoch gibt erste Funktion deutlich mehr Randinformationen und beschleunigt so sehr schnell das Verständnis für den Code. Wie lang eine Funktionsbezeichnung oder ein Variablenname ist, hat keinerlei Auswirkung auf die Ausführungsgeschwindigkeit – denn beim Kompilieren werden die Namen sowieso verworfen. Also warum geizen, wenn man diese Fähigkeit gut ausreizen kann. Natürlich sollte man es auch nicht übertreiben und zum Schluss Funktionen wie “todayIsAGoodDaySoGiveMeTheUserData()” nehmen 😉

Dokumentation des Codes ist natürlich immer ein leidiges Thema. Ich persönlich bin eigentlich immer ganz froh, wenn wie oben beschrieben Variablen und Funktionen selbsterklärend sind und sich doch ab und zu eine Kommentarzeile im Code verläuft. Wenn ich dann noch eine zusätzliche Dokumentation erhalte, die mir grob einen Gesamtüberblick über das System verschafft, bin ich schon glücklich. Als Entwickler kann man sich, mit etwas Erfahrung, sehr schnell in Systeme “reindenken” – gibt es dann dazu eine automatisiert generierte 1000-Seiten Doku, wird zumindest bei mir der gegenteilige Effekt erziehlt.

Sätze wie “Kommentare schreibe ich dann später rein” kennen wir wohl zuhauf – und seien wir mal ehrlich: in diesem Fall wird es nie einen Kommentar geben. Ein guter Ansatz ist daher, zuerst den Klassen- oder Methodenrumpf zu bauen, grob zu dokumentieren und erst dann mit der Implementierung zu beginnen.
Optimalerweise kann man abschließend noch ein paar erläuternde Kommentarzeilen verpacken und schon hat man wieder deutlich zur Verständlichkeit beigetragen.

Fazit
Ein nettes Zitat, welches ich kürzlich gelesen hatte, sagt folgendes: “Entwickle immer so, als ob der Typ, der den Code später warten muss, ein Psychopat ist, der deine Adresse hat.”. Besser kann man es wohl nicht formulieren 😉

Links:
Geek&Poke (Bildquelle)

statische Code-Analyse mit dem php code sniffer

Foto von Dennis Wegner

Programmierer machen, wie jeder andere Mensch, auch Fehler. Nun gibt es zum einen Fehler in der Syntax, auf der anderen Seite gibt es die Logik-Fehler. Beide verursachen, dass ein Programm entweder gar nicht oder nicht wie erwartet funktioniert. Normalerweise kann (und muss) man diese Fehler entdecken – manchmal geht das leicht, manchmal aber auch nicht. Syntax-Fehler stellen hier den einfachsten Kandidaten dar, da diese meist schon von der verwendeten IDE erkannt werden, spätestens aber bei der Ausführung des Programms ein Fehler auftritt. Logische Fehler sind da schon etwas kniffliger, da sie rein aus Programmiersicht fehlerfrei sind. Nur die Art und Weise, wie sie ein Problem behandeln entspricht nicht dem, was der Programmierer ursprünglich vor hatte.

Zu diesen beiden Kandidaten gesellt sich aber noch eine weitere Gattung, nämlich die Art und Weise, wie Code geschrieben wird. Jeder Programmierer hat – genauso wie jeder Mensch die eigene Handschrift – einen eigenen Programmierstil. Das ist zuersteinmal nichts verwerfliches und wird weder Probleme in der Syntax noch in der Logik hervorrufen. So gesehen gibt es aus betriebswirtschaftlicher Sicht keinerlei Notwendigkeit, sich darum zu kümmern. Problematisch wird es aber dann, wenn mehrere Entwickler mit einem Projekt beschäftigt sind. Spätestens hier treffen Welten auseinander und es kann durchaus vorkommen, dass völlig korrekter Code einfach nicht mehr bzw. nur sehr schwer lesbar ist. Bei geringem Umfang stellt das nicht das große Problem dar, wenn man allerdings mit komplexen Projekten zu tun hat kann dies zu einem großen Hindernis werden. Das fiese daran ist, dass man das Problem anfangs einfach nicht erkennt. Wenn es dann auftritt, ist es meist zu spät.

Um dem Herr zu werden, setzen viele Unternehmen und Entwickler auf Coding-Standards. Dabei handelt es sich um Regeln, wie Code formatiert werden muss und welche Lösungswege nicht verwendet werden sollten. Oder eben, welche Prioritäten bestimmte Lösungswege haben. Dabei geht es z.B. um die Art und Weise, wie Klammern gesetzt werden, die Variablen formatiert werden, wie Klassen und Kontrollstrukturen (Schleifen usw.) auszusehen haben usw. Gerade große Open Source Projekte mit mehreren hundert oder gar tausend Entwicklern müssen einfach derartige Regeln definieren, da sonst reinstes Chaos entsteht. Das würde dann dazu führen, dass Code zwar sehr gut, aber aufgrund von schlechter Lesbarkeit nicht mehr wartbar wäre. Und somit leider unbrauchbar sein würde.

Ok, nun haben wir die Theorie geschafft. Nun kann man die Regeln definieren und jeder hält sich dran, aber wie es so bei den Menschen ist, unterlaufen einem Fehler, man wird faul oder man hat einfach keine Lust auf die Regeln. Die gegenseitige Kontrolle von Code mag bei kleineren Mengen noch durch Menschen machbar sein, wenn ein Projekt aber aus mehreren zehntausenden Zeilen Code besteht, ist das auch nicht mehr machbar. Und an dieser Stelle setzt man auf statische Code-Analyse. Mit den entsprechenden Tools kann man Code analysieren und anschließend anhand definierter Regeln auswerten lassen.

Eines dieser Tools, und das sagt ja schon die Überschrift, befasst sich mit der statischen Analyse von php Code und nennt sich “php code sniffer”. Das Tool ist kostenlos und kann über PEAR geladen werden:

pear install PHP_CodeSniffer-1.3.0RC2

Die Version 1.3 RC2 ist die derzeit aktuelle Version, um auf dem neusten Stand zu sein schaut doch einfach mal hier nach, welche die aktuelle Version ist.

Über die Console ist das Tool nach erfolgreicher Installation mittels “phpcs” verfügbar. Der Aufruf ist dann ganz leicht:

phpcs --standard=zend class.php

In diesem Beispiel wurde die Datei class.php auf die Coding-Standards von Zend hin überprüft. Das Ergebnis sieht dann ungefähr so aus:

FILE: /.../class.php
--------------------------------------------------------------------------------
FOUND 3 ERROR(S) AND 0 WARNING(S) AFFECTING 3 LINE(S)
--------------------------------------------------------------------------------
1 | ERROR | End of line character is invalid; expected "\n" but found "\r\n"
9 | ERROR | Expected 0 spaces before opening brace; 2 found
11 | ERROR | Spaces must be used to indent lines; tabs are not allowed

Als kurze Erklärung: In Zeile 1 wurde ein falscher Zeilenumbruch verwendet (kann passieren, wenn man mit Windows und Linux/Unix im Wechsel am gleichen Code arbeitet), in Zeile 9 waren zwei Leerzeichen vor der öffnenden geschweiften Klammer und in Zeile 11 wurde mit Tabs an Stelle von Leerzeichen eingerückt. Die Datei an sich funktioniert tadellos, allerdings verstoßen diese Punkte eben gegen die Konventionen von Zend.

Wie ihr seht, ist es mit dem php code sniffer sehr schnell und vor allem sehr einfach möglich, euren Code auf Regeln hin zu untersuchen und Fehler dieser Art zu finden. Neben den Regeln von Zend gibt es noch einige weitere vorgefertigte Rules, die ware Stärke zeigt das Tool aber mit der Möglichkeit, eigene Regeln zu definieren. Da ich den Umfang dieses Beitrags nicht sprengen will, schaut euch doch einfach mal folgenden Beitrag auf “php hates me” an, der genauer auf dieses Thema eingeht: PHP Code Sniffer – Eigene Regeln erstellen.

Links:
php code sniffer
phphatesme.com