Schöner hashen mit bcrypt
Gastartikel von Oliver Sperke.
Ich bin 34 Jahre alt und seit 10 Jahren selbständiger Webentwickler. Mein Fokus liegt dabei auf der Erstellung, Beratung und Optimierung in den Bereichen High Performance, Usability und Sicherheit in den gängisten Internetsprachen: PHP, HTML, Javascript und CSS.
Bei meinem vorherigem Gastbeitrag wurde ich direkt im ersten Kommentar aus meiner heilen Welt geworfen. Dort stand nämlich folgender „erschütternder Kommentar“ zu lesen:
Das du Salting und Mehrfachhashing predigst, während der Rest der Welt schon einen Schritt weiter zu bcrypt geht… Traurig.
Nun ja, dazu möchte ich drei Dinge sagen.
- Ich predige nicht (Ausnahme: „Es heißt Standard, verdammt, nicht Standart!“).
- Ach, wenn die Welt schon mal auf dem Stand des einfachen md5 wäre …
- Bcrypt verdient einen eigenen Beitrag.
Natürlich hatte der Autor völlig recht. Über Hashfunktionen im Web zu schreiben und bcrypt nicht zu erwähnen ist fast schon schändlich. Also bcrypt ist eine Hashfunktion, die auf Langsamkeit optimiert wurde. Um genauer zu sein, es ist nicht mal ein richtiger Hashalgorithmus, sondern eine Blowfish Verschlüsselung, bei der am Ende „die Schlüssel weggeworfen werden“, daher lässt sich das Ergebnis nicht mehr entschlüsseln. bcrypt ist eine Weiterentwicklung der „Traditional DES Scheme“ Funktion aus der Unixwelt. Obwohl dieses Verfahren 30 Jahre lang (!) gute Dienste geleistet hat, stellen sich so langsam „Alterserscheinungen“ ein. Der Zahn der Zeit nagt auch hier in Form von gestiegener Rechenleistung.
Kurze Rückschau
Hashalgorithmen wie md5 und die shaX sind auf Schnelligkeit optimiert. Das ist gut, wenn man prüfen will, ob der heruntergeladen Film auch wirklich korrekt übertragen wurde. Das ist auch gut, wenn man testen möchte, ob das SSL Zertifikat einer Webseite nicht verfälscht wurde. Das ist aber eher suboptimal, wenn man damit Passwörter speichern will. Wie schon im letzten Artikel erklärt und ausgiebig diskutiert, ist die gestiegene Rechenleistung auch ein Problem für die klassischen Hashfunktionen in Webanwendungen. Zwar kann man mit Salts, mehr Salts und Mehrfachhashes das Schlimmste verhindern, aber irgendwie ist das alles nicht so nachvollziehbar für jeden. Kurz gesagt: „Das geht besser“.
Was bcrypt kann
Selbst wenn wir dafür sorgen, dass unsere Passwörter gut geschützt sind, können wir natürlich kaum verhindern, dass ein Benutzer trotzdem ein schwaches Passwort verwendet. Wenn es der Angreifer gezielt auf eine Person, z. B. den Admin abgesehen hat, wird die Situation noch schlimmer, denn bis 10 Zeichen kann man auch schon mal eine Brute Force Attacke für einen einzelnen Hash probieren. Deshalb sollten wir unserem Cracker das Leben grundsätzlich so schwer wie möglich machen.
Bcrypt bringt gewisse Vorkehrungen für genau diesen Fall mit. Der Algorithmus ist auch optimiert noch sehr langsam, und das ist gut so. Der Hash beinhaltet einen Wert für die Rundenanzahl, den sog. „Kostenfaktor“. Dies ist der Aufwand der bei der Berechnung betrieben werden muss. Jedem Hash kann zusätzlich ein individueller Salt zugeordnet werden. In PHP ist bcrypt über die crypt() Funktion seit Version 5.3 fest implementiert. Davor hing der Einsatz vom Betriebsystem ab.
Was bcrypt nicht kann
Bcrypt bringt einige schöne Fähigkeiten mit, die wir in der Webwelt wunderbar nutzen können. Allerdings gibt es einige Dinge, die designbedingt nicht vorgesehen sind. Dazu zählt ein geheimer Salt, den wir in unserer Anwendung hinterlegen können. Der Sinn resultiert aus der Überlegung, dass wenn ein Angreifer aus welchen Gründen auch immer auf die Datenbank zugreifen kann, er auch noch auf den Quelltext der Webanwendung zugreifen können muss. Im Zweifel entscheidet sich dort, ob unsere Passwörter weiter geschützt sind oder nicht.
Die zweite fehlende Funktion, ist die Möglichkeit Hashes von zusätzlichen Faktoren, wie der E-Mail Adresse (ersatzweise dem Benutzernamen oder die UserID) abhängig zu machen. Der Hintergrund ist etwas speziell. Nehmen wir mal an, ein Angreifer findet in unserer Webanwendung eine XSS Lücke, mit denen er die Session eines Benutzers übernehmen kann. Was wäre das Schlimmste, was er tun kann? Richtig, er ändert das Passwort oder die E-Mail Adresse. Wie kann ich das am effektivsten verhindern? Ganz klar, ich muss ihn zwingen zur Überprüfung das alte Passwort einzugeben. Durch die Kopplung der Hashes an die E-Mail kann ich das gar nicht vergessen. Man könnte natürlich den Salt abhängig von der E-Mail machen, aber was ist wenn ein Benutzer mehrfach angemeldet ist? Brute Force Attacken werden mit jedem Ziel lohnenswerter.
Vom Rein und Raus
Ein ganz einfacher Hash ensteht so:
crypt ( 'Passwort', '$2a$04$EinSaltFuerDasPasswort' );
Als Ausgabe ergibt sich:
$2a$04$EinSaltFuerDasPasswore.oNHNUzZrs1V5tpdv/WJ64.DIyBV1kC
Auf den ersten Blick ist das im Vergleich zu md5(‚Passwort‘) natürlich etwas verwirrend, aber dafür schreibe ich das ja hier. Im ersten Argument steht der zu hashende String. Das zweite Argument besteht aus drei Teilen, die je mit einem $ eingeleitet werden. Der erste Block bestimmt die verwendete Funktion. Die möglichen Werte könnt Ihr auf php.net nachschauen. Wir nutzen hier nur $2a für bcrypt.
Der zweite Block beschreibt die Anzahl der Runden, mit dem der Hash erstellt wird. Der Wert darf zwischen 04 und 31 liegen. Mit jeder Runde verdoppelt sich die Zeit zur Erstellung, das System ist also exponentiell. Wenn eine Runde etwa 1 ms dauert, dann dauern 31 Runden ca. 74 Minuten. Genug Luft nach oben also. Brauchbare Werte liegen derzeit bei etwa 08 bis 12, je nach eingesetzter Hardware und Geduld. Gibt man Zahlen ausserhalb des Bereichs an, wird *0 zurück gegeben.
Der dritte Block ist der individuelle Salt. Dieser darf aus Groß- und Kleinbuchstaben, Zahlen, sowie ./ bestehen. Tauchen andere Zeichen auf, wird ebenfalls *0 zurück gegeben. Die Eingabewerte müssen also gut gewählt sein. Weiterhin darf der Salt aus 128 Bits, also 21 1/2 Zeichen bestehen. Wen das verwundert, 21 Zeichen werden komplett dargestellt, beim letzten Zeichen werden die Hälfte der Bits verworfen. Deshalb wird aus ‚EinSaltFuerDasPasswort‘ im hash ‚EinSaltFuerDasPasswore‘.
Die Ausgabe entspricht der Eingabe, gefolgt vom eigentlichem Hash. Jetzt kann man natürlich berechtigt fragen, was daran sicher sein soll, wenn da ja alles steht. Stimmt, aber genau dieses Verfahren ist gleichzeitig ein Vorteil.
Einmal bcrypt …
Ich erstelle der Einfachheit halber eine Funktion, mit der man die Eigenschaften von bcrypt richtig nutzen kann. Auch hier gilt wieder – nichts ist in Stein gemeisselt. Wenn Ihr Vorschläge habt, her damit. Die Funktionen nenne ich (besonders kreativ) bcrypt_encode und bcrypt_check.
function bcrypt_encode ( $password ) { return crypt ( $password, '$2a$04$EinSaltFuerDasPasswort' ); }
Diese Funktion gibt uns einen ersten Anfang. Ein Aufruf von bcrypt(‚Passwort‘) gibt uns den o. g. Hash zurück. Die Saltfunktion nutzt natürlich überhaupt nichts, wenn man überall den gleichen Salt verwendet. Da der Salt in der Datenbank steht und daher nicht geheim ist, kann dieser pseudozufällig sein kann. Folgendes Konstrukt ist also ausreichend.
$salt = substr ( str_shuffle ( './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ) , 0, 22 );
Die Anzahl der Runden sollte variabel sein. Der Hintergrund ist ganz einfach. Manche Accounts sind wichtiger als andere. Wenn ich als Admin 3 Sekunden zum Login warten muss, dann stört mich das nicht. Einem Besucher diese Wartezeit zu erklären, könnte sich aber schwierig gestalten oder als technische Schwäche fehlinterpretiert werden. Als Bonbon kann man dem Besucher auch anbieten, die sichere Variante zu wählen. Ein Normalwert sollte festgelegt werden, aber mit der Möglichkeit zu abweichenden Werten. Übertragen auf unsere Funktion ergibt sich.
function bcrypt_encode( $password, $rounds='08' ) { $salt = substr ( str_shuffle ( './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ) , 0, 22 ); return crypt ( $password, '$2a$' . $rounds . '$' . $salt ); }
Zur Passwortspeicherung habe ich jetzt eigentlich alle Möglichkeiten von bcrypt ausgeschöpft. Mit jedem Aufruf wird ein neuer Hash mit einem anderem Salt erzeugt. Nur sicherer fühl ich mich jetzt nicht, denn genau wie oben schon erwähnt gebe ich dem Angreifer freiwillig alle Daten. Es ist natürlich besser als ein purer md5 hash, aber eher noch gut gemeint als gut gemacht.
Daher würde ich diese Funktion gerne erweitern. Wie bei unserem md5 Beispiel bringe ich zusätzlich einen Salt ein, der nur im Quelltext hinterlegt ist. Dieser muss vor dem ersten Aufruf der Funktion mit define(‚SALT‘, ‚beliebigerWert‘) definiert werden. Ausserdem möchte ich, dass man bei einer Änderung der E-Mail Adresse das alte Passwort eingeben werden muss. In den Kommentaren zum letzten Beitrag hat ein Besucher erwähnt, dass das Einbringen eines Salt mit hash_hmac() sicherer wäre als einfaches voranstellen oder anhängen. Auch wenn ich die Bedenken in diesem speziellem Fall nur bedingt teile, da sich der individuelle Salt in jeder Zeile ändert und daher ein Angriff auf den systemweiten Salt sinnlos wäre, schaden kann es auch nicht und wenn wir schon einmal dran sind, klotzen wir mal richtig. 😉
Dazu erweitern wir zunächst einmal mit str_pad() unseren String auf die viefachefache Länge des Passwortes, indem wir ihn mit dem sha1 hash der E-Mail Adresse davor und dahinter auffüllen. Ich nehme hier sha1, weil ich möchte, dass sich bei jeder E-Mail der Salt komplett ändert. Diesen String jagen wir dann durch hash_hmac() mit unserem systemweiten Salt in Whirlpool als Binärausgabe. Den entstandenen Zeichensalat verpacken wir mit bcrypt.
function bcrypt_encode ( $email, $password, $rounds='08' ) { $string = hash_hmac ( "whirlpool", str_pad ( $password, strlen ( $password ) * 4, sha1 ( $email ), STR_PAD_BOTH ), SALT, true ); $salt = substr ( str_shuffle ( './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ) , 0, 22 ); return crypt ( $string, '$2a$' . $rounds . '$' . $salt ); }
Ein Aufruf von
bcrypt_encode( 'oliver@anonsphere.com', 'Test-Null8Fünfzehn' );
führt also zu diesen Ergebnissen:
// Passwort mit E-Mail auf vierfache Länge aufgefüllt 4e707693b984367edadf5a022867Test-Null8Fünfzehn4e707693b984367edadf5a022867e // hash_hmac mit Whirlpool und systemweitem Salt (im Original binär) b0492febbd81ef10387e2e7127295e09c197ff0cadf8ae5dc98182178f9e0891cf86b91abb1c723e0de510361cb67e1149460a687271672d77de439d7c572b57 // Verpackt mit bcrypt $2a$08$jb8v67zmCNMO9dlX1tkVqOxGlhQkJNL45AvfpbEWvqXnGC8YcO7Hm
Die Sicherheit dürfte nur schwer anzuzweifeln sein. 🙂
… und zurück
Jetzt fehlt noch die Möglichkeit den gespeicherten Passworthash mit unserem Passwort zu vergleichen. Jetzt kommen wir zu dem Teil, wo bcrypt von nett, auf cool wechselt.
$2a$08$jb8v67zmCNMO9dlX1tkVqOxGlhQkJNL45AvfpbEWvqXnGC8YcO7Hm
Schauen wir uns doch mal diesen Hash genauer an. Wir haben oben gesehen, der Hash besteht aus den Einstellungen und dem Ergebnis. Anders gesagt, der Hash liefert uns alles, was wir zum Berechnen brauchen. Dazu schreibe ich ihn mal anders, damit es klarer wird.
Einstellungen: $2a$08$jb8v67zmCNMO9dlX1tkVqOx Hash: GlhQkJNL45AvfpbEWvqXnGC8YcO7Hm
Der erste Teil entspricht genau dem Code, den wir zur Erzeugung genutzt haben, daher muss auch bei korrekter E-Mail und Passwort und diesen Einstellungen unser Hash heraus kommen. Dass heißt, wenn der hinterlegte hash die Variable $stored hat, dann muss folgende Bedingung wahr sein.
crypt ( $string, substr ( $stored, 0, 30 ) ) == $stored;
Oder übertragen in eine eigene Funktion.
function bcrypt_check ( $email, $password, $stored ) { $string = hash_hmac ( "whirlpool", str_pad ( $password, strlen ( $password ) * 4, sha1 ( $email ), STR_PAD_BOTH ), SALT, true ); return crypt ( $string, substr ( $stored, 0, 30 ) ) == $stored; }
Der Vorteil erschliesst sich erst auf den zweiten Blick. Während man bei der Umstellung von md5 auf sha1 oder von sha1 auf sha256 Probleme mit bestehenden Benutzerlogins bekommt, weil die alten Passwörter ungültig werden, nimmt uns bcrypt alle nötigen Workarounds ab. Bei der Prüfung ist es nämlich vollkommen egal, was als Ursprungswert an Runden und Salt hinterlegt wurde. Wenn ich irgendwann mal die Sicherheit erhöhen will oder auf einen schnelleren/langsameren Server umziehe, ändere ich den $rounds Wert und trotzdem funktionieren alte Logins weiter. Sobald aber die E-Mail oder das Passwort geändert wird, wird der neue Wert übernommen.
Was noch zu sagen wäre
Bcrypt ist eine schöne Sache, entbindet Euch aber keineswegs von zusätzlichen Sicherheitsüberlegungen. Wer zukunftsorientiert an ein neues Projekt geht oder wer schon beim letzten Artikel überlegt hat, ob seine Passwortspeicherung wirklich so sicher ist, wie er dachte, sollte überlegen, ob der Einsatz lohnt. Ein sicheres Verfahren deshalb abzulösen, ist aber auf jeden Fall unnötig.
Bleibt mir abschliessend nur noch eins zu sagen: „ES HEISST STANDARD!!11“.
Hallo, super Artikel.
Eine Frage stellt sich mir noch. Und zwar, macht es einen Unterschied ob ich diesen Kostenfaktor erhöhe oder stattdessen einfach das ganze durch eine Schleife laufen lasse. Vorteile? Nachteile? So sah das bei mir bis jetzt aus.
$password = „passwort123“;
$salt = substr(str_shuffle(‚ABCDEFGHIJ……………..qrstuvwxyz0123456789′),0,22);
$key = „AAAAAAAAAABBBBBBBBBBCC“;
$hash = sha1($password.$salt.$key);
for($i=0;$i kleiner 100;$i++){
$hash = crypt($hash,’$2a$07$‘.$key.’$‘);
}
Auf mich wirken diese paar zeilen eig. ganz sinvoll.
Was sagt ihr? (ich ahne pöses :D)
ricH
17 Jun 12 at 06:15
Joah, kann man machen – muss man aber nicht, denn wie Du schon bemerkt hast, dafür ist eigentlich der Kostenfaktor da. (ich sehe jetzt aber auch keinen Nachteil).
Oliver
17 Jun 12 at 13:29
Hallo Oliver,
ich glaube du willst mich nicht verstehen, deshalb nur noch eins. Ein Satz wie „Beweise, dass es angreifbar ist“ zeugt nur davon, dass du dich nicht viel mit IT-Sicherheit auseinander gesetzt hast. IT-Sicherheit läuft nach dem Prinzip, dass ein Verfahren Jahrelang getestet wird, weltweit von etlichen Sicherheitsexperten und dann langsam als sicher akzeptiert wird und nicht nach dem Motto, ich mache da mal was eigenes, denn das geht zu 100% schief. Es sei denn, man hat in dem Bereich promoviert und sein halbes Leben der Zahlentheorie gewidmet.
Max
13 Aug 12 at 21:48
Das ist kein neuer Hashalgorithmus, sondern ein Verfahren, dass auf gängigen Empfehlungen und bestehenden Algorithmen beruht. Da die Maßgabe der Schutz des Originalpassworts ist, kann das Verfahren als so sicher gelten wie der stärkste verwendete Algorithmus. Ist bcrypt angreifbar, ist es immer noch so sicher wie whirlpool usw.
Standardisierte Verfahren alleine (sagen wir nur bcrypt) haben immer den Nachteil, dass diese oft von Standardprogrammen berechnet werden können (Bsp. http://www.openwall.com/john/) und es deshalb für eine breitere Basis angreifbar ist. Das ist schlecht. Dieses Verfahren hier legt die Messlatte schon bedeutend höher.
Und daher nochmal: Sag mir, wo ich einen Fehler gemacht habe bzw. finde einen Sicherheitsfachmann, der mir sagt, wo ich einen Fehler gemacht habe und ich ziehe meinen Vorschlag sofort zurück.
Ich plane aber schon einen Folgeartikel, der noch mal einen drauf setzt. Ich komme nur derzeit nicht dazu, ihn auszuarbeiten.
Oliver
13 Aug 12 at 22:54
[…] wählt. Selbst OpenVPN bietet standardmässig nur einfaches Hashing mit SHA1 oder MD5 an – das ist zu wenig! AnonSphere funktioniert so, dass die Daten durch den Datenbanktreiber in Freeradius umgeschrieben […]
8 gute Gründe, warum AnonSphere ein guter VPN Anbieter ist | PHP Gangsta - Der PHP Blog mit Praxisbezug
28 Sep 12 at 09:59
[…] zwischen alten Verfahren wie MD5/SHA und neueren Algorithmen wie bcrypt. Dank Artikeln zum Thema bcrypt von Oliver sollte auch jedem mindestens eine Lösung bekannt sein die man heutzutage einsetzen sollte. Doch […]
Hilfe, ich habe unsichere Passwörter in meiner Datenbank! | PHP Gangsta - Der PHP Blog mit Praxisbezug
2 Okt 12 at 10:59
[…] bringt prinzipiell alles mit, um anständiges Hashing zu betreiben. bcrypt ist der Way To Go, aber auch ohne bcrypt kann man mit vernünftigem Einsatz der vorhanden […]
Sicheres Hashing in PHP | David Müller: Webarchitektur
11 Okt 12 at 10:00
Hallo Oliver,
Meiner Meinung nach wird in dem Beitrag zu sehr auf dem Salt herumgeritten und damit letztlich auch deutlich, dass der Autor nicht wirklich verstanden hat, wozu ein Salt da ist.
Der einzige Zweck eines Salts besteht darin zu verhindern, dass der Angreifer aus den für den Angriff auf einen User gewonnenen Daten einen Nutzen für den Angriff auf einen anderen User ziehen kann. Dazu muss der Salt (oder Teile davon) nicht geheim sein, das Ziel ist auch mit einem bekannten Salt je User absolut erreicht. Mehr geht hier nicht.
Das Ziel der Passwortverschlüsselung bzw. des Passworthashens ist es auch nicht, es einem Angreifer möglichst schwer zu machen. Es muss unmöglich sein, in einem vertretbaren Zeitrahmen aus den erbeuteten Daten Passworte zu gewinnen. Mit BCrypt erreicht man dieses Ziel über den Work-Faktor. Damit kann man den Aufwand, ein einzelnes Passwort per Brute-Force zu ermitteln (bei MD5/SHA geht das binnen Sekunden/Minuten) in die Region von Jahren und Jahrzehnte rücken. Dadurch erhält der Site-Betreiber genügend Zeit um bei einem Userdatenklau entsprechend zu reagieren.
Ein serverweiter geheimer Salt bringt hier rein gar nichts, weil man immer im Sinne der Sicherheit davon ausgehen muss, dass auch dieser dem Angreifer in die Hände gefallen ist. Es gibt keinen vernünftigen Grund, bei einem Angriff vom Gegenteil auszugehen.
D.h. auch mit einem solchen serverweiten Salt hängt die Sicherheit einzig und allein am verwendeten Algorithmus, hier also an BCrypt. Hat man hier sichere Parameter verwendet, dann sorgt ein serverweiter Salt für keinen weiteren Sicherheitsgewinn mehr, denn mehr als „in einem vertretbaren Zeitraum unhackbar“ geht nicht und wird auch nicht benötigt.
Hat man einen leicht angreifbaren Algorithmus oder schwache Parameter verwendet, dann wäre es vollkommen idiotisch, jetzt noch darauf zu hoffen, dass dem Angreifer der serverweite Salt nicht in die Hände gefallen ist. Genauso gut könnte man darauf vertrauen, dass der Angreifer zu blöd ist, eine Brute-Force-Attacke zu fahren. Jeder sicherheitsbewusste Admin nimmt die Site jetzt sofort vom Netz.
Und man könnte sogar behaupten, dass der serverweite Salt eher noch schadet, weil er den entwickelten Code unnützerweise etwas komplizierter macht, wodurch sich auch die Möglichkeiten von Programmierfehlern in einem wesentlichen Teil der Anwendung erhöht. Das gilt insbesondere auch für das Mehrfachhashen per Schleife, oder andere unnütze Befrachtung, wie z.B. die Verknüpfung mit der E-Mail-Adresse.
Auch die Idee mit der XSS-Lücke und einer so erreichten Session-Übernahme ist ein absurdes Szenario. Das schlimmste ist hier nicht die Möglichkeit, das Passwort zu ändern (denn das würde ja wenigstens auffallen), sondern die Sessionübernahme selbst. Hier ist dem Angreifer ja ganz ohne Userdatenbank das gelungen, was der Brute-Force-Angreifer ebenfalls versucht: auf eine Benutzersession zuzugreifen bzw. eine zu erzeugen. Wie man das verhindert, hat allerdings mit dem Thema dieses Beitrages nichts zu tun.
Auch unterschiedlich Work-Faktoren für unterschiedliche Userklassen ist Unsinn. Ein Admin ist nicht schützenswerter als ein normaler User, da es die Aufgabe des Admins ist, die User zu schützen. Wenn der Userspace kompromittiert ist, dann ist der Worst-Case bereits erreicht, weil dann der Adminspace seinen Sinn eingebüsst hat. Es ist allenfalls zu verschmerzen, wenn nur einzelne User als kompromittiert gelten. Ein einheitlicher niedriger Work-Faktor für die User setzt aber alle User auf die Abschussliste. Kurz: der Workfaktor muss immer so hoch sein, dass weder Admin noch User kompromittiert werden können. Und hier ist der mediale GAU, der Eintritt, wenn etliche User kompromittiert wurden, noch gar nicht angesprochen.
Auch der letzte Hinweis, auf den Vorteil den BCrypt in Bezug auf die Erhöhung der Sicherheit nach langjährigem Betrieb per einfacher Änderung des Work-Faktors im Programmcode, weil dann die „alten“ Passworte noch funktionieren, kann man so nicht stehen lassen:
Der Work-Faktor ist nicht abhängig von der Hardware, die der Betreiber der Site einsetzt, sondern von der Hardware, die der Angreifer einsetzen könnte. Wenn der Server für das Hashen des PW mit einem sicheren Work-Faktor zu langsam ist, dann ist es höchste Zeit für einen Hardware-Upgrade, da auf dem Server der Work-Faktor ein weit geringeres Problem darstellt, als für den Angreifer. Aus dem selben Grund kann man auch nicht einfach nur den Work-Faktor im Anwendungscode ändern und den ursprünglichen Work-Faktor für alte Passworte in der Datenbank bestehen lassen, denn entscheidend für die Sicherheit ist letztlich, wie sicher die Passwort-Hashes der User sind. Als Angreifer würde ich mir zuerst die älteren und weniger sicheren aussuchen. Und es ist relativ einfach, beim nächsten Login des Users (der ja das PW im Klartext eingibt) das PW neu zu hashen. User, die sich über einen längeren Zeitraum nicht einloggen, sollte man irgendwann deaktivieren. Und genau das ist auch mit anderen Algorithmen nicht viel schwerer zu implementieren.
Was noch zu sagen wäre:
PW-Hashes, die auf SHA, MD5 usw. basieren, sollte man heute als unsicher betrachten, egal ob mit oder ohne Salt, und man sollte deswegen z.B. zu BCrypt oder anderen Verfahren, die einen Work-Faktor berücksichtigen, wechseln.
TomW
30 Okt 12 at 16:34
SELECT * FROM `user` WHERE `email`=’$email’ AND `password`=’$password’
ist auch mit prepared_statements keine gute Idee, wenn man Indices einsetzt, weil mit jeder PW-Änderung unützerweise der Index aktualisiert werden muss. Den Abgleich des PWs kann man auch per PHP machen, nachdem man den User per
SELECT * FROM `user` WHERE `email`=?
geholt hat.
TomW
30 Okt 12 at 16:49
Hast Du den Artikel gelesen, weil darum ging es ja? 🙂
Wie soll er denn reagieren? Die Benutzerdaten sind doch schon weg. Deshalb ist es ja essentiell, die Hürden so hoch wie möglich zu legen.
Das ist natürlich
richtignicht ganz falsch, aber es gibt durchaus Szenarien, in denen das nicht so ist, z. B. wenn nur die Datenbank geleakt wird. Die Passwörter darin sind völlig wertlos, wenn der systemweite Salt fehlt. Und aus meiner Erfahrung kann ich sagen, dass man an eine Datenbank meist viel schneller kommt als an Serverdaten.Ich glaube, Du hast die Problematik nicht ganz verstanden. Wenn Userdaten verloren gehen, dann ist nicht die größte Gefahr, dass die sich jemand auf der Seite anmeldet, sondern dass die Passwörter auf anderen Seiten verwendet werden. Da kannst Du Deine Seite 100 mal vom Netz nehmen. Wenn jemand „geheim“ als Passwort hat, dann lohnt sich auch eine Wörterbuch Attacke und wenn er bei Paypal die gleiche Mail/Pass Kombi hat, dann nutzt es ihm auch nichts, wenn er dass Passwort auf Deiner Seite ändern muss.
Und Lebensmittelkonsum führt in vielen Fällen zu Lebensmittelvergiftung …
Auch das ist nicht ganz richtig. Sagen wir, wir sind in einem sozialem Netzwerk und übernehme die Session. Dann kann ich im Namen meines Opfers posten. Aber ich kann den Account erst dann übernehmen, wenn ich E-Mail und Passwort ändere. Das kann ich aber nur, wenn ich auch das Passwort kenne.
Was aber nicht heißt, dass er nicht höher sein darf, oder?
Nach Deiner Aussage muss also jedes Forum mit 100 Leuten auf ’nem dediziertem Server laufen, weil es sonst unsicher ist. Willkommen in der echten Welt, kann ich da nur sagen.
Natürlich sollte man die irgendwann mal updaten, aber darum ging es hier gar nicht. Ausserdem waren wir uns ja einig, dass der normale Workfaktor schon ausreichend hoch sein sollte. Wenn morgen nicht jemand mit einem Quantencomputer aus ’nem Labor springt, wird der bei Berechnungszeiten von mehreren Mio. Jahren auch in den nächsten 50 Jahren nicht unsicherer.
Womit wir wieder am Anfang sind. Hast Du den Artikel gelesen, weil darum ging es ja? 🙂
Oliver
30 Okt 12 at 17:22
Hallo Oliver,
z.B. indem der die Nutzer benachrichtigt und sie bittet, ihr Passwort zu ändern. Bei einem sicheren Hash kann er ihnen dann auch versichern, dass die Passworte so schnell nicht ermittelt werden können und sie deswegen keine Panik schieben müssen.
Die einzige Möglichkeit, die Hürde zuverlässigerweise hochzulegen, ist ein hoher Work-Faktor. Wenn Du zusätzlich noch den geheimen Saltzusatz brauchst, hast Du ein Problem.
Wenn Du als Entwickler eine Anwendung entwickelst, kannst Du nicht allein von diesem Szenario ausgehen. Die Passworte müssen auch für den Fall geschützt sein, wenn dem Angreifer der systemweite Salt bekannt ist. Wenn Du die Passworte auch für diesen Fall absicherst, dann ist der systemweite Salt überflüssig, weil er keinen weiteren Sicherheitsgewinn mehr bringen kann. Tust Du es nicht, sind die Passworte nur noch in dem oben beschriebenen Szenario sicher.
Das ist richtig. Ich habe mich der Einfachheit halber nur auf das Szenario beschränkt, dass nur Usernamen/E-Mail-Adressen und Passworthashes abhanden gekommen sind und was die Folgen sind, wenn das Passwort ermittelt wird. Die sind für alle Sites, die die selbe Username-Passwort-Kombination verwenden selbstverständlich gleich. Ich dachte, dass das nicht gesondert erwähnt werden muss sondern klar ist.
Möglichst einfachen und gut verständlichen Code zu entwickeln ist keine schlechte Praxis, die zu mehr Sicherheit beiträgt, oder willst Du dem wirklich widersprechen? Wenn zusätzlicher Code keinen echten Nutzen bringt, dann sollte man auf ihn verzichten.
Ja, keine Frage. Nur beginnt der Worst-Case bereits mit der Übernahme der Session und nicht erst dann, wenn der Angreifer die Userdaten ändert. Man kann nicht pauschal sagen, solange der Angreifer die Userdaten nicht ändern kann, ist noch nicht das schlimmste passiert, denn es hängt von dem Service ab, den die Site bietet, womit man dem User am schlimmsten schaden kann. Wenn der Angreifer z.B. einmalig eine Bestellung für 1000 Euro aufgibt, dann dürfte das für viele User bereits der Super-Gau sein.
Richtig. Es stellt sich nur die Frage, welcher Nutzen damit erzielt werden soll. Wenn der Work-Faktor bereits für die User so hoch ist, dass die Passworte sicher sind, dann bringt ein höherer Work-Faktor keinen Nutzen mehr. Bringt er einen Nutzen, dann stellt sich die Frage, warum die User einem geringeren Schutz ausgesetzt werden, wenn alles, was es zu schützen gilt, am Ende immer die Userdaten sind.
Das ist nicht dein Ernst: Wie oft sollen sich denn die 100 User am Tag ein- und ausloggen, damit die Serverlast so ansteigt, damit dieses Szenario einen Realitätsbezug hat? Wenn sich jeder 2x am Tag einloggt, dann verbraucht das Login der 100 Leute bei einem Work-Faktor, der 0,3 Sekunde Rechenzeit verursacht, 1 Minute Rechenzeit auf einem einzelnen Core. Das Cracken eines einzelnen Passworts bei der selben Rechenleistung benötigt hingegen 12 Jahre. Selbst wenn Du die tausendfache Rechenzeit einsetzen kannst, dann braucht der Angreifer zur Ermittlung eines einzigen Passworts immer noch 4 Tage für alle 100 User braucht er so mehr als 1 Jahr. Und das alles unter der unrealistischen Annahme, dass es sich um einfache kurze Passworte mit 6 Zeichen handelt.
Ich gehe mal sehr schwer davon aus, dass die vielen andere Sachen, die die User auf dem Server machen, bei steigender Userzahl sehr viel eher einen weiteren Server notwendig machen, als der Login-Prozess.
Ich hatte hingegen am Ende den Eindruck, dass Du den Nutzen von BCrypt wieder ein wenig in Frage stellen wolltest.
Aber noch einmal auf den serverseitigen Salt zurückkommend:
Warum setzen wir eigentlich Hashes ein statt die Passworte einfach zu verschlüsseln? Weil wir dann ein Geheimnis in Form eines Verschlüsselungspasswortes auf dem Server hinterlegen zu müssten. Jeder, der an dieses Verschlüsselungspasswort und die Userdaten gelangt, der kann dann die Userpassworte einfach entschlüsseln, egal, wie sicher das Verschlüsselungsverfahren ist.
Hashes entbinden uns von der Notwendigkeit, ein Geheimnis auf dem Server zum Schutz der Nutzerpassworte hinterlegen zu müssen. Deswegen ist es vollkommen absurd, in Form von geheimen serverseitigen Salts dieses Problem wiederzubeleben. Passworte müssen so gehasht werden, dass man sie möglichst nicht per Brute-force in annehmbarer Zeit ermitteln lassen. Dann sind diese Salts überflüssig. Alles andere ist Security by Obscurity und damit unsicher.
TomW
30 Okt 12 at 21:32
Nee, um jetzt nicht alles noch mal zitieren zu müssen und um es auf einen Nenner zu bringen. Es ist (IMHO) nicht schädlich mehr zu tun als nötig. Grundsätzlich spricht auch nichts gegen Pepper oder gegen die Nutzung von verschiedenen Algorithmen. Teilweise sind die sogar nötig, weil bcrypt selbst ein paar Schwächen hat. Ausserdem hälst Du Scriptkiddies davon ab, mit JTR rum zu experimentieren.
Genau so ist es bei den Sessions. Natürlich sollte der Rest nicht unsicher sein, aber ein Trapezartist lässt auch nicht das Netz weg, weil die letzten Jahre nichts passiert ist. Wenn man den Login als abgeschlossenes Modul sieht, ist selbst meine Version nicht so komplex als dass es da Probleme geben könnte.
Wenn Du also einen konkreten Fehler findest, der meinen Vorschlag unsicher macht, ok. Aber pauschal zu sagen, dass etwas unsicher wird, weil es zusätzlichen Schutz bietet, macht in meinen Augen wenig Sinn.
Oliver
31 Okt 12 at 05:02
Ich zitiere mal dies
http://www.martinstoeckli.ch/hash/de/
„Geben wir noch etwas Pfeffer dazu
Mit Pfeffern meint man das Kombinieren einer geheimen Zeichenfolge mit dem Passwort, bevor man den Hash-Wert berechnet.
$scharfesPasswort = $passwort + „p8empspher3ocm1yUwiq“;
$hash = bcrypt($scharfesPasswort);
Der Pfeffer (Pepper) ist geheim und wird nicht in der Datenbank gespeichert. Stattdessen wird er an einem möglichst sicheren Ort hinterlegt, der gleiche Pepper gilt für alle Passwörter.
Kennt der Angreifer unseren Code (Kontrolle über den Server), so bringt der Pepper keinerlei Vorteile.
Hat der Angreifer nur Zugriff auf die Datenbank (SQL-Injection), so erkennt er immer noch die Hash-Werte, die Hash-Werte stammen aber nicht mehr von schwachen Passwörtern. Es sind Hash-Werte von langen Kombinationen von Passwort und einem starken Pepper. Kein Wörterbuch enthält je solche Passwörter, ein Wörterbuchangriff ist darum sinnlos.
Häufig wird empfohlen einen HMAC zu verwenden, um Passwort und Pepper zu kombinieren. Werden sie hingegen einfach aneinandergehängt, so sollte der Pepper hinter und nicht vor dem Passwort angefügt werden, da manche Hash-Funktionen Zeichen ab einer bestimmten Position ignorieren.“
ECMA-262-3rd
4 Feb 13 at 15:10
[…] Eigenschaften (dynamischer Salt, langsamer Algorithmus gegen Brute-Force) direkt mitbringt: bcrypt. PHP 5.5 führt dazu sogar eine einfach zu nutzende Password Hashing API ein, von der […]
Salt and Peper - php.de
7 Feb 13 at 12:55
TomW und andere Vorposter haben es ja schon geschrieben – ich will nur nochmal für andere darauf hinweisen und betonen:
Ich würde den obigen Artikel NICHT als Anleitung für eine Eigenimplementierung nehmen.
1) es gibt dafür eine fertige PHP-Libraries
2) die Generierung des Salts ist Mumpitz. Der sollte *nirgendwo* fix hinterlegt werden, um damit z.B. den Random-Salt zu erweitern oder so Späße – sondern sollte ein REINER Zufallswert sein – und zwar nicht Applikationsweit sondern für jeden einzelnen Eintrag neu. Das str_shuffle ist zwar naheliegend, aber wie schon angemerkt zu kurz gedacht – da Dopplungen unnötigerweise verhindert werden. Man erzeuge eine Zufallszahl und konvertiere die in das spezielle base64-Format von bcrypt. Auch dafür gibts fertige Funktionen.
3) Die Vorverarbeitung des Passworts (mit whirlpool) ist unnötig, da bcrypt selbst für die Streckung des Passwortes sorgt. Ich empfinde das sogar als schädlich. BCrypt-Hashes sind über sprachgrenzen hinweg lesbar. Das, was da fabriziert wird aber nur bedingt – könnte also ggf. später nicht mal eben in ein anderes System übernommen werden.
4) Die Rundenzahl „04“ aus dem Beispiel sollte ja wohl *nirgendwo* genommen werden
Evtl. sollte nochmal klar sein: Der Salt sollte pro Eintrag eindeutig sein – und sollte nichts mit den anderen Einträgen gemeinsam haben.
yves
14 Mai 13 at 22:39
Das ist eine Begründung? Ich kann es bei Github hochladen, wenn Du Dich dann besser fühlst.
Nein, ist es nicht.
Kannst Du lesen? Es ist ein Zufallswert!
Macht genau welchen Unterschied? Richtig, keinen.
Hast Du überhaupt eine Ahnung, wovon Du redest? Wie lang darf ein Wert sein, dass er mit BCrypt richtig gehasht wird? Na?
Whirlpool ist genau so standardisiert wie BCrypt. Wir können auch noch ein paar Sonderfälle raus suchen, nachdem man unbedingt md5 nehmen muss.
Kannst ja 12 nehmen, am besten auf einem Webspace, wo es dann 10 Sekunden dauert. 04 ist u. U. aber ebenfalls ausreichend, was im Algorithmus von Bcrypt begründet ist.
Oliver
14 Mai 13 at 23:09
Ich bin selbst Webentwickler und ich finde dieses Thema hier echt amüsant. Jedoch läuft hier einiges drunter und drüber.
Der Salt dient nicht der Verlängerung der Berechnungszeit sondern soll nur das Vorabberechnen einer RainbowTable verhindern. Daher muss dieser für jedes Passwort unterschiedlich sein. Der Salt sollte eine zufällige Zeichenkette sein.
Es ist natürlich Sinnvoll zu verhindern das in einer gestohlenen Session keine wichtigen Änderungen durchgeführt werden können (Passwort ändern, Email Adressen ändern, wichtiger Einstellungen ändern) jedoch ist dies nicht Aufgabe einer Authentifizierungsklasse sondern der eigentlichen Applikation.
Der Hash Algorithmus sollte nur das Passwort und den Salt selbst hashen, dies übernimmt die php Funktion crypt().
Das danach wird noch geprüft ob der erzeugte Hash dem gespeicherten Hash entspricht und fertig ist die Sache.
Eine Veränderung des Hash Algorithmus im laufenden Betrieb ist eigentlich weder mit md5() shaX() bcrypt() ein Problem. Beim Login wird geprüft wie der Hash gespeichert ist. Wenn noch die alte Version gespeichert ist wird das Passwort mit dem alten Algorithmus überprüft, mit neuen neu gehasht und gespeichert, der alte Hash gelöscht. Ansonsten wird nur mit dem neuen Hash überprüft.
Eine gute Authentifizierungsklasse sieht den Wechsel des Algorithmus bereits vor. Ob gesondert gespeichert wird oder nicht welcher Algorithmus verwendet wird ist egal. Jedoch ist es eventuell Sinnvoll alle relevanten Parameter in einer Spalte zu Speichern, da eventuell neuere Algorithmen mehr Parameter benötigen, als zum Zeitpunkt des Datenbankdesigns bekannt war.
Zum Thema Security by Obscurity, dort gehört dein Beitrag leider hin:
Es ist davon auszugehen, dass dem Angreifer ohnehin alle gespeicherten Parameter bekannt sind (Hash, dynamischer Salt, statischer Salt, Algorithmus, Rundenzahl etc).
Es ist zwar richtig, dass die Datenbank ohne den festen Salt Bestandteil wertlos ist. Jedoch wird ein Angreifer der überhaupt die Ressourcen besitzt um bcrypt mit ausreichender Rundenzahl zu knacken nicht daran scheitern alle relevanten Informationen in seinen Besitz zu Bringen.
Die Scriptkiddies mit ihren 1337 Maschienen scheitern schon an einem dynamischen Salt mit ausreichender Rundenzahl.
Der Rest gehört einfach nicht in den Hashalgorithmus oder die Authentifizierungsklasse.
Die Funktionen die Sie an bcrypt bemängeln sind
1. unnötig, da unwirksam
2. in md5() und shaX() ebenfalls nicht vorhanden
3. auch in bcrypt können Sie an die zu hashende Zeichenkette beliebige Parameter anhängen (wie Emailadresse oder statischen Salt)
Ich weiß, dass es Schwierig ist mit Kritik umzugehen, Sie benehmen sich jedoch wie ein kleines Kind.
Sehen Sie doch einfach ein, dass Ihr Algorithmus Verbesserungswürdig ist, es sollen keine persönlichen Angriffe sein, sondern Verbesserungsvorschläge. Gerade im Bereich Sicherheit sollte man Versuchen das Beste zu Programmieren oder zu Verwenden, nehmen Sie sich das Gesagte zu Herzen und überlegen Sie wie sie Ihren Algorithmus noch verbessern können. Ein kleiner Hinweis von php selbst zur crypt() Funktion:
Kurz zusammengefasst sollten Entwickler „$2y$“ bevorzugt verweden, wenn sie nicht mit PHP Versionen vor PHP 5.3.7 kompatibel sein müssen.
Ich habe mir erlaubt Sie hier noch einmal aus ihrem oberen Beitrag zu Zitiren:
Kurz gesagt: „Das geht besser“.
Balint
21 Mai 13 at 19:36
Beispiele? Es ist ja nun oftmals so gewesen, dass Datenbanken von Sony, Samsung, Yahoo usw. veröffentlicht wurden. Wann wurde dabei auch Applikationsquelltext offen gelegt? Oder anders: Wo schränkt die Vorgehensweise die Sicherheit der Passwörter ein?
Sofern ein Angreifer auf meinen Server direkt zugreifen kann, sind meine Benutzerdaten verloren, denn er könnte sich auch alle eingegebenen Passwörter zuschicken lassen. Wenn aber, wie im Normalfall „nur“ die Datenbank verloren geht, dann ist das ein prima Schutz vor allem für die, die einfach zu erratende Passwörter nutzen.
Sagt wer? Schränkt es die Sicherheit ein, wenn man elementare Fehler von vorne herein vermeidet?
Wie sehen denn die Verbesserungen aus? Bisher ist noch in keinem Kommentar ein echter Schwachpunkt genannt worden.
Der Beitrag ist 4 Monate vor PHP 5.3.7 erschienen. Meine Glaskugel hat mir das damals nicht verraten, sorry. Allerdings spielt es für aktuelle PHP Installationen keine Rolle, ob man 2a oder 2y verwendet. In beiden Fällen kommt das gleiche raus.
Oliver
21 Mai 13 at 20:35
Nur weil Seitenquelltext nicht veröffentlicht wird, heißt es nicht, dass kein Zugriff darauf vorhanden war. Jedoch werden auch viele Internetseiten über FTP gehackt und die Datenbank dann mittels hochgeladener Scripte extrahiert. Es ist richtig das die Sicherheit nicht verringert wird, jedoch erhöht sie sich auch nicht.
Ein höherer work factor ist hier immernoch das Mittel der Wahl. Zusammen mit Passwortregeln welche nach Möglichkeit sehr Konservativ gewählt werden um die Mehrzahl der Benutzer nicht zu verärgern.
Nein es schränkt in diesem Fall die Sicherheit nicht ein, jedoch ist es einfach kein sauberes Programmieren, wenn Aufgaben von Modulen vorgenommen werden, für die sie eigentlich nicht vorgesehen sind. Dies führt zu unübersichtlichem Code welcher aufgrund von Fehlern wieder Sicherheitslücken öffnen kann.
Nur weil sie glauben, dass es keine echten Schwachpunkte sind heißt es nicht, das es wirklich keine sind. Gemeint ist ihre Funktion welche den Salt erzeugt.
Wie bereits von anderen erwähnt schließen Sie so effektiv einen sehr großen Teil des möglichen Schlüsselraumes aus. In der Tat ist der Sicherheitsverlust hierdurch nicht gravierend. Jedoch können bei der Vorberechnung von RainbowTables so sehr viele mögliche Salt Zeichenketten außgeschlossen werden.
Wie Bereits erwähnt kostet eine richtiger Zufallsstringgenerator keine 5 Zeilen mehr Code noch 2 Minuten längere Arbeit.
Ich habe in der Tat nicht geschaut von wann der Artikel ist, jedoch ist dies eine weitere mögliche Verbesserung. Dass es keine Rolle spielt kann man so nicht stehen lassen. 2a ist unter bestimmten Umständen eine fehlerhafte Hashfunktion, 2x ist die Hashfunktion welche gravierende Fehler hat und der 2a Implementierung von vor 5.3.7 entspricht. Neue Hashes sollten immer mit 2y angelegt werden. Wenn sie anderer Meinung sind sollten sie doch php.net den Vorschlag unterbreiten die offizielle Dokumentation zu ändern.
Balint
21 Mai 13 at 22:32
Ich denke, der hier vorgestellte Vorschlag (mehr ist es ja nicht) ist nicht so komplex, als das er die Sicherheit beeinträchtigen könnte. Ausserdem muss jeder Entwickler selbst entscheiden, wo eine bestimmte Programmlogik hinterlegt ist. Würde ich 3 Entwickler zu dem Thema fragen, bekäme ich sicher auch 4 unterschiedliche Antworten.
Der Pepper ist durchaus umstritten, allerdings gibt es Szenarien, in denen er sinnvoll ist. Von daher halte ich es für zu einfach, ihn als unsinnig abzutun. In meinen Augen, gibt es keinen Grund, darauf zu verzichten.
Na, die Frage ist doch, wie wirkt es sich aus? Ist die Wahrscheinlichkeit, dass ein Salt doppelt ist gegeben. Rechnerisch vielleicht, aber praktisch?
Ein Befehl reicht schon:
Aber! Das Hinzufügen einer Konstante macht den Code unnötig komplex, aber ein 5 Zeilen langer Zufallsgenerator nicht? Interessant!
Nö, muss ich gar nicht, die stimmen nämlich mit mir überein:
For practical purposes, it does not really matter if you use $2a$ or $2y$ for newly set passwords, as the countermeasure is only triggered on some obscure passwords (not even valid UTF-8) that are unlikely to be seen outside of a deliberate attack (trying to match hashes produced by buggy pre-5.3.7 code).
http://www.php.net/security/crypt_blowfish.php
Oliver
22 Mai 13 at 00:09
Das ist überhaupt nicht der Punkt. In dem Fall ist das richtig, ändert aber nichts an der Tatsache das soetwas kein schöner Programmierstil ist und auch gegen alle gängigen Konventionen verstößt. In einer Hashfunktion hat außer dem Passwort und dem Salt nichts etwas zu Suchen und dieser Salt muss auch Zufällig sein, dies ist bei einer Emailadresse nicht gegeben.
Anscheinend bemängelt eine Vielzahl von Entwicklern, wie dem Kommentarbereich eindeutig zu entnehmen ist, dieses Feature. Nicht die Funktion an sich ist schlecht, sondern die Implementierung.
Prinzipiell kann ich trotzdem eine neue Emailadresse in der Datenbank speichern, es ist ja nicht so, dass dieser Algorithmus exklusiven Zugriff auf die Datenbank hat.
Nur bei der Änderung des Passwortes wird eine Emailadresse zum hashen benötigt und der Algorithmus kann ja wohl kaum entscheiden ob dieser aus einer Benutzereingabe stammt oder aus einer Session Variabelen.
Die neue Mail kann man trotzdem in der Datenbank speichern, dies hat dann zufolge, dass sich der Benutzer nicht mehr einloggen kann, da die Emailadresse mit der der Hash berechnet wurde nicht mehr mit der übereinstimmt, die er zum einloggen verwendet hat.
Erreicht wurde nur, dass man bei der Änderung der Mail den Hash neu berechnen muss.
Das eingegebene Passwort muss sowieso Validiert werden, sonst würde die Eingabe keinen Sinn ergeben. Wenn es valide ist wird die Mail geändert, sonst nicht.
Nein eine Kollision ist nicht gemeint, die ist Unwahrscheinlich und wäre auch bei einer sicheren Erzeugung möglich, um dies unnötigerweise auszuschließen müsste man in der Datenbank prüfen ob es schon Vergeben wurde.
Gemeint ist das weniger als 2^128 RainbowTables berechnet werden müssen, da alle Schlüssel/Salt bei denen ein Zeichen doppelt vorkommt von vornherein ausgeschlossen werden kann. Somit vergibt man einen Teil des Vorteils des Salt. 2^80 ist zwar immernoch sehr sehr viel, jedoch bedeutend weniger als 2^80. Genaugenommen nicht mal ein Bruchteil. In zukunft kann man eventuell solche RainbowTables erstellen.
Eine ordentliche Lösung wäre:
function generateSalt($length = 22)
{
// Check to see if OpenSSL libraries
if (function_exists(‚openssl_random_pseudo_bytes‘)) {
return bin2hex(openssl_random_pseudo_bytes($length));
}
// Use less-secure salt-generation method.
else {
error_log(‚php-scrypt warning: OpenSSL not installed!‘);
$salt = “;
$chars = ‚abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#%&*?‘;
$num = strlen($chars) – 1;
for ($i = 0; $i kleiner $length; $i++) {
$salt .= $chars[mt_rand(0, $num)];
}
return $salt;
}
}
Das Hinzufügen einer Konstante macht den Code nich unnötig komplex, beim Pepper geht es um Sinn oder Unsinn.
Dort steht das es nicht wirklich wichtig ist aber, dass ein Unterschied vorhanden ist.
Balint
22 Mai 13 at 01:27
Also erst einmal muss ein Salt einmalig sein, woraus der besteht, ist zweitrangig, da der Salt nicht geheim ist.
Ich habe zwei gezählt, aber ok, die Alternative habe ich ja in die Kommentare geschrieben.
https://www.phpgangsta.de/schoener-hashen-mit-bcrypt#comment-51878
Genau das war ja auch der Sinn der Sache. 😉 Aber wie gesagt, das ist (m)eine Version. Die Alternative findet sich in den Kommentaren.
Es sind immer mehr als 2^128 (woher kommt die Zahl?) … wie lange dauert die Berechnung einer Regenbogentabelle bis 8 Zeichen in bcrypt bei fast utopischen 10.000 Passwörtern pro Sekunde? So knapp 400 Jahre etwa, richtig? Und das mal ~10^35 als realistische Option? Echt? Aber wenn Zeichen doppelt vorkommen können und man daher 10^39 Tabellen berechnen müsste, wäre es plötzlich sicher?
Okay … möchten Sie da noch mal nachbessern oder soll ich die Funktion sofort in der Luft zerreissen? Übrigens die Klasse aus der das „geliehen“ ist, benutzt auch pepper. Nur mal so angemerkt. Der Pull Request bei Github zeigt auch sehr schön, dass der Verfasser (Sie?) dieser Funktion absolut keine Ahnung hat, was er da tut und warum er es tut.
https://github.com/DomBlack/php-scrypt/pull/7
Oliver
22 Mai 13 at 03:48
Sie Wiedersprechen sich selbst. Sie bemängeln die Cryptografische Sicherheit von mt_rand() welche zugegeben nicht sehr hoch ist, daher wird ja auch wenn verfügbar die sichere Alternative genutzt. Nutzen selbst den Cryptografisch noch unsicheren Zufallsgenerator str_shuffle() welcher auf rand() basiert zusammen mit statischen Informationen.
http://stackoverflow.com/questions/14079703/str-shuffle-and-randomness
Die bessere Alternative ist Ihnen nicht Gut genug, selbst nutzen Sie jedoch eine Bedeutend schlechtere. Entscheiden Sie sich mal.
Balint
22 Mai 13 at 06:20
Ok, also bis vor ein paar Minuten habe ich die Kritik wirklich noch Ernst genommen. 🙂 Das Problem mit zufälligen und pseudozufälligen Generatoren ist folgendes:
– Bei den (echt)zufälligen kann man den Wert nicht voraus sagen.
– Bei den pseudozufälligen kann man die Werte mit einer gewissen Wahrscheinlichkeit voraus sagen (unter bestimmten Bedingungen)
Pseudozufällige Generatoren sollten nicht für kryptografische Werte eingesetzt werden, soweit richtig. Aber ein Salt ist kein kryptografischer Wert. Kryptografie kommt von geheim. Ist der Salt geheim? Nein, er steht ja in der Datenbank. Es ist also völlig egal, wo der Wert her kommt, so lang er einmalig ist. Dafür reichen pseudozufällige Werte völlig aus. Wenn ein Angreifer den Salt voraus sagen kann, kann er damit das Passwort voraus bestimmen? Nee, oder? Wenn ein Angreifer das Passwort auf einen unbekannten Wert zurück setzt, der mit einem bekannten Salt gehasht ist, hat er dadurch irgendwas gewonnen? Nee, oder?
Ok, versuchen wir die „bessere“ Alternative mal:
– generateSalt(22)
Wenn ich openssl installiert habe, bekomme ich einen 44 stelligen hexadezimalen Wert, von dem die Hälfte der Zeichen weg fällt, richtig? Der Zeichenraum besteht dann nur noch von 0 bis f, statt aus dem base64 Zeichenraum. Ihre Funktion reduziert damit die Summe der Möglichkeiten von 10^35 auf ~10^26 um fast 1/3. Das ist die bessere Alternative? Die Möglichkeiten würden sicher noch ausreichen, aber es ging ja um eine „Verbesserung“. Wo ist die Verbesserung?
Ok, sagen wir, ich habe openssl nicht installiert, dann erzeuge ich mit mt_rand einen Wert aus dem angebenem Zeichenraum, sagen wir „Y#Vix3DSa&AHeE9oJlgPrR“. Wissen Sie, was mir bcrypt zurück gibt, wenn ich das als Salt angebe? Richtig *0 → Fehler. Sobald nämlich nur ein Zeichen nicht aus dem base64 Zeichenraum besteht, gibt die crypt Funktion nur einen Fehler zurück.
Das ist also die bessere Alternative? Eine Alternative, in der ich entweder den Zeichensatz massiv reduziere oder ca. 20 % der Werte einfach alles zulasse?
Oliver
22 Mai 13 at 11:58
Danke für den Artikel und auch Danke für die tolle Diskussion, auch wenn es teils echt anstrengend war, sie komplett zu lesen. Einige Ansätze fand ich schon gerechtfertigt und zum nachdenken hervorragend, was mir jedoch komplett fehlt sind Fakten. Es wird immer nur gesagt, es gibt bessere Variante, es gibt Libraries und es gibt fertige/bestehende Funktionen. ABER nicht ein einziger Name fällt oder wird erwähnt. Ich erwarte wirklich keine kompletten Anleitungen oder Beschreibungen, aber wenigstens ein Name, nachdem man dann googlen kann, wäre echt was feines. Ich wollte mich die ganze Zeit nun schon mit dem Thema sicherere Passwörter in einer Applikation beschäftigen und nun muss/darf ich es endlich machen. Ich wollte weg von md5 und wusste, es gibt sha1, wusste aber auch, dass es auch nicht mehr so sicher ist und musste hier an sha512 denken. Das dies aber auch nicht gerade das gelbe vom Ei ist und der Weg im Grunde anders bestritten werden muss, habe ich in diesem Artikel gelernt und dafür danke ich!
Wie gesagt, fand ich einige Ansätze auch gut und einige Sachen im Code hätte ich von vornherein auch etwas anders gemacht. Teils wurden diese Punkte in den Kommentaren auch angesprochen und auch Ausbesserungsvorschläge gegeben. Fand ich gut. Was mich da aber viel mehr interessiert, da es ja teils auch um die Performancefrage ging, welche der Varianten ist die bessere?
Ich will jetzt gar nicht darüber diskutieren, welcher Salt nun sinnvoll ist oder nicht, das ist im Grunde egal, mich interessiert hier eher, wie optimiere ich die Performance zur Erzeugung dieser. Darüber hat irgendwie niemand ein Wort verloren, immer nur über den „work factor“.
substr(str_shuffle()),
substr(str_shuffle(str_pad())),
for()
oder gar eine komplett andere Funktion, die vielleicht sogar etwas fertiges zurück gibt. Warum versucht man nicht hier schon einen Optimierungsansatz? Ok der Artikel ist im Grunde ja auch nur ein Beispiel, wie man es machen kann, doch ich finde gerade die Kommentare sind dafür perfekt, hier zusammen eventuell die perfekte Lösung zu ermitteln und zu erstellen. Oder?
Ansonsten versuche ich diese Ansätze nun erst mal selber in einigen Tests zu nutzen und sage nochmal danke für den Artikel 🙂
maXus
16 Aug 13 at 16:36
Ich hoffe, dass möglichst viele Kommentare, die bestätigen, dass dies keine gute Lösung ist, helfen, zu verhindern, dass unwissende Benutzer dies so implementieren.
Oliver wird sich sowieso nur drehen und wenden und auf stur schalten, also hoffe ich einfach, dass die Masse der Kommentare dominiert und unwissende Benutzer abschreckt:
1. /dev/random verwendet Maus, Keyboard, Sound, Gerätetreiberrauschen, CPU-Temperatur, Spannungsschwankungen uvm. und ist damit true random, nicht pseudo wie Oliver behauptet.
Quelle: http://en.wikipedia.org/wiki//dev/random
2. Dein Argument „Zeige mir eine Schwäche“:
Du bist nicht der erste, der die Email-Adresse mit hashen möchte, das ist immer die erste Frage der Studenten in den Security-Vorlesungen. Und irgendwann verstehen sie auch, warum es nichts bringt und warum sie es nie in einen Standard geschafft hat:
Sie bringt keinen Sicherheitsvorteil.
und:
Alles, was keinen Sicherheitsvorteil bringt in diesem Bereich, ist Verschwendung von Ressourcen und Verschwendung von Zeit bei der Implementierung, die man dazu verwenden könnte, wirklich die Sicherheit zu erhöhen.
3. Von 100 Leute, die selbst Kryptografie implementieren, macht es nur einer richtig. Selbst wenn du es hier also richtig erklären würdest, würden 99/100 einen Fehler einbauen, du verursachst also, dass andere unsichere Webseiten haben.
4. Nutzt fertige Security-Libraries, die sich etabliert haben. So eine Library kommt raus und wird jahrelang überarbeitet und diskutiert. Ein einzelner Mensch kann sowas niemals schaffen. Ja, Oliver, du kannst deinen Algo auf github laden, da wird er aber dahinvegetieren und das weißt du auch, du stellst dich nur stur. Denn alleine hier hast du ja schon 5 Kommentare, die bestätigen, dass es schlecht ist, was du tust, nehme es doch einfach mal an und versuche an dir zu arbeiten!
Greg
19 Nov 13 at 16:59
Mimimimimi! /dev/random ist pseudozufällig. Steht sogar in dem Link, den Du gepostet hast. Warum ich das mit den E-Mails mache, hab ich doch erklärt, oder? Machen muss man das nicht, Sinn ergibt es trotdem. Wenn ich immer auf das was geben würde, was irgendwer, irgendwann im Internet geschrieben hat, wäre ich wohl ständig mit Unwichtigem beschäftigt. Ansonsten mimimimimi (ohne Argumente).
Oliver
19 Nov 13 at 17:56
zu 2) Das es keinen Vorteil bringt, hat er ja nun auch schon gesagt, jedoch auch keinen Nachteil. Weder in Zeit noch Ressourcen (ok evtl. ein wenig beim letzteren).
zu 3) Das er hilft unsichere Websites zu generieren, halte ich für etwas sehr schnell geschossen. Immerhin regt er zum Denken an und hilft mit einem Weg weg vom md5 und sha1, den noch immer so gut wie ALLE Scripte nutzen.
zu 4) Hast du ein Beispiel, welche sich etabliert haben, damit man sich da mal einarbeiten/lesen kann? Würde mich jetzt schon interessieren. Danach googlen kann ich zwar, ob sich die Ergebnisse dann aber etabliert haben, kann ich dann sicherlich aber nicht wissen. Du scheinst dich ja mit dem Thema auszukennen und daher würde ich jetzt mal auf einen Tipp von dir hoffen. Gibt es von dir zu dem Thema etwas zu lesen?
Von deinen hier 5 erwähnten Kommentaren, welche die Unsicherheit bestätigen, finde ich jedoch keinen. Ich sehe nur Behauptungen. Keine Nachweise oder Quellen. Hoffe habe die Kommentare noch alle richtig im Kopf.
Allgemein:
Nach den letzten Meldungen von Adobe, mache ich mir auch aktuell wieder vermehrt Gedanken dazu, wie man Daten in der Datenbank am besten verschlüsselt. Eventuell hast du ja auch dafür einen Tipp.
maXus
19 Nov 13 at 19:04
Warum substr($stored,0,30) und nicht 29?
(unabhängig von der Tatsache, dass der Rest der Zeichen wohl eh verworfen wird und man substr() gar nicht benötigt)
$2a = 3 Zeichen
$08 = 3 Zeichen
$ = 1 Zeichen + 22 Zeichen (21 1/2)
ergibt 29 Zeichen nicht 30.
Haben ich einen Denkfehler oder ist es egal?
HerrZ
29 Nov 13 at 09:00
Oli du bist süß, 34 Jahre und diskutierst wie ein kleines Kind. Allerdings sind die, die vergeblich versuchen, dich eines besseren zu belehren auch nicht gerade erwachsener.
Manu
11 Mrz 14 at 02:36
Ja, beim letzten Kommentar war ich auch etwas genervt, weil da auch wirklich alles falsch war, was mein Vorredner von sich gegeben hat. Meistens atme ich erst mal durch, bevor ich etwas schreibe. 😉
Oliver
11 Mrz 14 at 02:58
„Jetzt kommen wir zu dem Teil, wo bcrypt von nett, auf cool wechselt.“
YMMD!
RaveKev
14 Mrz 14 at 07:59
[…] […]
php Mysql $GET action mit "_"
15 Mrz 14 at 11:25
str_shuffle() basiert auf rand() und generiert nur 32 Bit random numbers. D.h. str_shuffle bietet nie mehr als 32 Bit. Auch wenn du damit ein 22 Zeichen Salt generierst. Es kommen nur 2^32 verschiedene Salts raus. Du könntest also auch nur 6 Base64 Zeichen generieren und hättest die gleiche 32 Bit-Sicherheit.
32 Bit ist heute vielleicht noch akzeptabel in Zukunft aber sicher nicht mehr. Rainbowtables bis 64 Bit sind nicht unrealistisch.
Ich empfehle dir auf meine Vorposter zu hören und Security nicht selbst zu implementieren, wenn du kein Spezialist auf dem Gebiet bist. Du dachtest jetzt beispielsweise du hättest einen ungefähr 90 Bit Salt, dabei sind es nur 32 und vielleicht gibt es noch mehr Schwachstellen in deinem Code, die du übersehen hast. Deshalb macht man so etwas nicht selbst.
Marc
3 Jun 14 at 13:29
Ich habe alle Kommentare mit viel „Freude“ gelesen. Da wird über einzelne Bits philosophiert und darüber gesprochen ob mit SALT oder ohne, aber dass sich jemand der Kommentatoren die Mühe macht, teile seines „besseren“ Codes zu publizieren, sehe ich nirgends. In Codingforen findet man Kommentare fast immer mit einer besseren Lösung (bzw. Vorschlag), nur hier wird über 2^32 gegen 2^64. Es wird um jedes halbe? bit diskutiert.
Solange wir Bugs im OpenSSL, Apache oder MySQL haben ,GET Variablen immernoch direkt an die DB übergeben werden und md5 hashes als Passwort in der DB stehen, müssen wir uns wohl kaum sorgen über die Verwendung eines 32Bit SALTs machen, da ja der SALT eh Sinnlos ist, wie in einem Kommentar erwähnt wurde.
Ich denke, man darf kritisieren, wenn man etwas besser machen kann und es auch als Diskussionsgrundlage zur Verfügung stellt. Möchte man nur Klugsch***** kann man dafür auch in ein crypingforum gehen.
Ich bin jedenfalls Dankbar für den Code, denn er erspart mir eine Menge Zeit, denn die Erläuterungen dazu genügen in jedem Fall um seine eigene Sichtweise zu Implementieren und ihn sicherer zu machen, wenn man es denn möchte.
Dabei sollte man dann möglichst nicht vergessen das Root Passwort seines Servers nicht nur mit „kleinbuchstaben“ versehen 😉
Stefan
15 Jul 14 at 13:21
„ES HEISST STANDARD“
ja, aber es heißt nicht „Gangsta“ 😉
fghdfgh
10 Aug 14 at 11:46
@Stefan
Und wenn du wie vorgeschlagen, etablierte Bibliotheken oder zumindest anerkannte Standards verwenden würdest (die in diesen Kommentaren bereits erwähnt wurden) und die seit Jahren existieren und trotzdem weiterhin Sicherheitlücken enthalten, weil niemand perfekt ist und dich nicht auf Amateur-Code-Schnipsel verlassen würdest, die mit Sicherheit sehr viel unsicherer sind, dann hättest du mit noch weniger Aufwand noch mehr Sicherheit erreicht. Die Welt braucht auf keinen Fall Code-Schnipsel in Blog-Kommentaren, um mehr Sicherheit im Internet zu erreichen.
Marc
12 Dez 14 at 13:09
Ich kann die Diskussion in den Kommentaren, ob sich Salts lohnen oder nicht, absolut nicht verstehen. Salts sind in jedem Fall eine sinnvolle und in meinen Augen unabdingbare Erweiterung zur Steigerung der Sicherheit. Im Detail ist das hier auch ganz gut erklärt: http://code-bude.net/2015/03/30/grundlagen-sicheres-passwort-hashing-mit-salts/
Raffael
12 Apr 15 at 12:31
@Marc
„[…] etablierte Bibliotheken und anerkannte Standards […] (die in diesen Kommentaren bereits erwähnt wurden)“
Ach so? Schade, dass Du nicht so freundlich warst, sie nochmal kurz zu wiederholen – viele können es eigentlich nicht gewesen sein, sonst wären sie mir sicher aufgefallen.
In dem einzigen Codebeispiel zum Thema stand mal was von „OpenSSL“ um einen wirklich zufälligen Salt zu erzeugen. Warum man das aber machen sollte ist sicher irgendwo in dem Bemühen untergegangen, Oliver als ahnungslose Pussy dastehen zu lassen – wie so ziemlich alle anderen Begründungen und vor allem stichfesten Belege der eigenen (Gegen-)Meinung wohl auch.
So ist mir auch nach aufmerksamem Lesen der ganzen Diskussion noch nicht klar, warum es nicht reicht, dass der Salt eindeutig pro Benutzer ist. Warum muss er darüber hinaus absolut zufällig erzeugt sein? Er steht ja schließlich nachher in der Datenbank direkt neben dem Passwort-Hash, wo ihn jeder lesen kann, der auch gerne den Hash knacken möchte. Und ob es wirklich attraktiver wird, tage- oder wochenlang Rainbow-Tabellen zu erzeugen nur weil zufällig zwei Nutzer des Forums/Onlineshops/etc. denselben Salt haben, das wage ich mal zu bezweifeln – ganz davon abgesehen, dass das selbst bei „echten“ Zufallszahlen passieren könnte.
Ich kann jedenfalls das, was Oliver hier schreibt, recht gut nachvollziehen und verstehen, während die Kritiker hier sich, wie Stefan schon angedeutet hat, in dieser Diskussion nicht eben mit Ruhm bekleckern und mich eher ratlos zurücklassen. Da sie leider nicht von ihrem hohen Ross heruntergestiegen sind und uns die Namen ihrer so sicheren und etablierten Bibliotheken verraten haben, muss ich das Ganze dann halt doch „zu Fuß“ mit der crypt-Funktion machen. Schade, aber auch nicht weiter schlimm.
Uwe
27 Jun 15 at 21:40
[…] bringt prinzipiell alles mit, um anständiges Hashing zu betreiben. bcrypt ist der Way To Go, aber auch ohne bcrypt kann man mit vernünftigem Einsatz der vorhanden […]
Sicheres Hashing in PHP | David Müller: Webarchitektur
11 Jan 16 at 23:23
@Uwe
Die Standards, die mehrfach genannt wurden, du aber zu faul bist nachzulesen, sind:
bcrypt (crypt)
(generiert automatisch bessere Salts als phpgangsta, er macht sich also Arbeit, um schlechtere Salts mit mehr Code zu generieren).
openssl
/dev/urandom
Alles besser, weniger aufwändig, als das hier beschriebene und Rainbow Tables werden nicht generiert, um ein einzelnes Forum zu hacken, sondern sie werden einmalig generiert, um dann sämtliche Passwörter, die, wie man ständig in der Presse hört, geklaut werden, zu hacken. Und ob du es glaubst oder nicht, diese Rainbow Tables existieren bereits und werden erweitert und genutzt.
Marc
29 Dez 16 at 11:23
Es ist kein Schaden, wenn man innem Salt neben zufälligen (und DB-öffentlichen) Dingen noch andere Dinge reinhaut (E-Mail Adresse, Konstante, die nur im Dateisystem (oder gar in einer speziellen Enklave) ist etc.). Security by Obscurity ist das noch nichtmal, da die Konstante „einfach“ ein weiteres Passwort ist.
Und die dadurch zusätzliche Komplexität ist nun wirklich minimal.
/dev/random kann in so einigen Fällen nicht verwendet werden, gerade bei Massenhostern. Da kann es einem schonmal passieren, dass so viele User Zufallszahlen wollen, dass blockiert wird. Wenn das der Fall ist kann man noch immer /dev/urandom verwenden – zwar sicherheitstechnisch nicht optimal, aber weil daraus keine Schlüssel gezogen werden, sondern „nur“ zusätzliches Salz sehe ich das nicht soooo kritisch.
mifritscher
25 Jun 17 at 11:43
Ein weiteres Problem der hier gezeigten Lösung:
Die Hashes werden hier mit „==“ verglichen, was gegen Timing-Attacken anfällig ist. Hierfür sollte unbedingt die Funktion „hash_equals“ verwendet werden. Mehr dazu hier:
https://security.stackexchange.com/a/74552
Diese Funktion gibt es erst seit PHP 5.6 also erst nachdem dieser Artikel geschrieben wurde. Die Moral der Geschichte ist also:
Programmiert sowas nicht selbst, programmiert nicht mal die Authentifizierung selbst, nutzt fertige Frameworks oder oder zumindest eine Authentifizierungs-Bibliothek, denn die wird regelmäßig geupdated und schließt auch zukünftig mögliche Angriffe. Eine Person alleine, kann nicht alles wissen. Entweder ihr schreibt ein Framework, oder eine Bibliothek oder eine Webseite, versucht nicht alles auf einmal zu machen. Um eine sichere Authentifizierungs-Lösung zu schreiben, muss man Erfahrung mit etlichen Angriffen haben und auf dem aktuellen Stand bleiben, um auch zukünftige Angriffe abwehren zu können!
Warum schreibe ich das? Weil dieser Beitrag immer noch oben bei Google auftaucht und ich Benutzer davon abhalten möchte, diese Fehler zu begehen!
Marc
1 Apr 19 at 14:59
Was haltet Ihr davon einfach Benutzname und Passwort nicht zu speichern!? Dann müßt Ihr Euch keine Gedanken mehr machen 😉 dafür natürlich wie sich die User trotzdem einloggen können … wer solch ein System mal testen möchte antwortet einfach auf meinen Kommentar 🙂
davidSTERN
8 Nov 19 at 15:00