PHP PW plain Übertragung gängige Praxis?

  • Ersteller Ersteller derBobby
  • Erstellt am Erstellt am
Daaron schrieb:
Ich hab bisher immer nur Lösungen mit einem einzelnen Salt gesehen, der dafür dann eben wenigstens 10-12 Zeichen lang is.
wundert mich, denn ein Salt pro User ist der Best-Practise.
Hast du nur einen Salt für alle reicht ein Bruteforce-Angriff bei dem du jedes mögliche PW hashst und mit denen aus der Datenbank vergleichst, bingo! Es ist nur ein Bruteforce-Durchlauf für alle Nutzer nötig. Während bei einem Salt pro Nutzer, du für jeden Nutzer einen eigenen Bruteforce-Durchlauf machen musst, das sind himmelweite Unterschiede in der benötigten Rechenleistung.


Daaron schrieb:
Übrigens hat deine Variante mit userbasiertem Salt, der in der DB gespeichert wird, einen RIESIGEN Haken!
[...] Man hasht die PWs in der Datenbank doch nur, dass falls mal jemand in die DB eindringen kann, er keine praktikable Möglichkeit hat, an die Klartext-PWs zu kommen. Wenn nicht gehasht ist, stehts gleich in Klartext. Wenn nur gehasht wurde, ohne Salt, kann man den Hash per RT "problemlos" in Klartext umwandeln. Wenn du jetzt für jeden User einen eigenen Salt anlegen willst, dann lagerst du diesen Salt in der DB. Du lagerst also Hash und Salt in einer gemeinsamen, kompromittierten Lagerstätte. Schlecht...
Und wieoft wird nur die Datenbank kompromitiert? I.R. findet doch ein Einbrauch in den Server statt und somit hat der Angreifer gleich Zugriff auch alles.
Einfach nur ein globaler Salt der für alle Nutzer gleich ist hat schon das Problem, dass man an den gehashten Passwörtern erkennen kann, dass diese identisch sind. Ein Hash sollte aber keinerlei Zuordnung mehr ermöglichen.


Daaron schrieb:
Viel sinniger ist da EIN Salt, der in einer Config-Datei außerhalb der DB gelagert wird. In der DB sollte keine Möglichkeit stehen, wie man z.B. per FTP auf diese Datei (und andere) zugreifen kann.
Viel sinniger ist es nach deiner Aufassung [1] Salz und Pfeffer zu verwenden!
  • Salz: ein einmaliger Salt für jeden Nutzer
  • Pfeffer: ein Salt der für alle gilt
Somit hat man das beste aus beiden Ansätzen vereint. Und dann folgendermaßen lösen:
Code:
hash := hmac_sha1(salt_global, hmac_sha1(salt_user, userpw))



[1] Das kann man auch so betreiben, habe ich nichts dagegen, ist aber nur "security through obscurity". denn sobald der Salt bekannt ist, ist das Herausfinden der Klartext-Passwörter wieder effizient möglich. Moderne Grafiken schaffen 2 Milliarden MD5 Hashes pro Sekunde. Umso besser der Hashing-Algorithmus umso geringer die Durchsatzrate, das Problem bleibt aber. Es ist nur ein Bruteforce-Durchlauf für die gesamte Nutzerdatenbank nötig.
 
Zuletzt bearbeitet:
ice-breaker schrieb:
wundert mich, denn ein Salt pro User ist der Best-Practise.
Hast du nur einen Salt für alle reicht ein Bruteforce-Angriff bei dem du jedes mögliche PW hashst und mit denen aus der Datenbank vergleichst, bingo! Es ist nur ein Bruteforce-Durchlauf für alle Nutzer nötig.
OK, spielen wir's durch.

Du kannst keine Rainbow Table von allen möglichen (und wenigstens... sagen wir mal 8stelligen) Passwörtern in Kombination mit einem dir komplett unbekannten 16stelligen Salt erzeugen. So viel Rechenzeit kostet immer noch ein Schweinegeld, das bringt schon mal nichts.
Jetzt bleibt dir nur, ein beliebiges User-PW zu knacken (per Brute Force). Jetzt fängst du an, aus diesem PW + jedem möglichen Salt so lange zu hashen, bis endlich der Hash des Users, dessen PW du schon kennst, raus kommt. Dann hast du den Salt. Dabei erzeugst du aber massig Serverlast und rasselst so oder so früher oder später in Login-Sperren.

Oh, und um so etwas zu verhindern hashst du deine User-PWs einfach nicht nur einmal sondern sagen wir 10x. Ein zulässiger Login dauert dann statt meinetwegen 10ms 100ms. Ein Brute Force dauert statt 1 Jahr plötzlich 10 Jahre.

Und wieoft wird nur die Datenbank kompromitiert? I.R. findet doch ein Einbrauch in den Server statt und somit hat der Angreifer gleich Zugriff auch alles.
Wenn er Zugriff auf alles hat, dann ist eh alles verloren. Dann hat er auch deine Salts, egal wo sie liegen.
Aber: Es ist überraschend leicht, nur die DB zu kompromittieren, ohne dabei Zugriff aufs File System zu erlangen. Genau das passiert ja bei SQL Injection Angriffen. Wenn natürlich der SQL-User auch noch die Möglichkeit hat, eine Form von Execute-Befehlen an die Shell zu senden, wars das...

Einfach nur ein globaler Salt der für alle Nutzer gleich ist hat schon das Problem, dass man an den gehashten Passwörtern erkennen kann, dass diese identisch sind. Ein Hash sollte aber keinerlei Zuordnung mehr ermöglichen.
Woran willst du da was erkennen? Der Hash ist ja immer noch md5($pw.$salt). Jedes PW erzeugt hier einen einzigartigen Hash (solange der Hash-Algorithmus kollisionsfrei ist)

Wie gesagt: die einfachste Lösung ist doppeltes Hashing. Das hebelt z.B. schonmal alle RT-Ansätze mehr oder weniger aus. Oder hast du schonmal ne RT gesehen, in der die Hashes aller möglichen Hashes stehen? Das wird ne große RT....
 
Daaron schrieb:
Wenn er Zugriff auf alles hat, dann ist eh alles verloren. Dann hat er auch deine Salts, egal wo sie liegen.
genau, du hast es erfasst. Und mit einem Salt pro User ist da nichts verloren. Denn egal wie teuer jede Hashing-Operation ist oder auch wie billig. Es muss für jeden Nutzer ein komplett eigener Bruteforce-Durchlauf gestartet werden. Du hast nun nur einen globalen Salt und hashst also das Passwort "a" mit dem Salt den du durch den Einbruch gewonnen hast und vergleichst das Ergebnis mit allen Nutzern. Und du findest dann sehr schnell die ersten Passwörter weil einfach schwache Passwörter genommen wurden.
Wenn aber jeder Nutzer einen eigenen Salt hat geht das einfach nicht. Du kannst also mit jedem Passwort-Hash den du erzeugst nur versuchen genau das Passwort eines bestimmten Nutzers zu knacken und nicht der gesamten Datenbank.
Da und genau da liegt der Vorteil.


Daaron schrieb:
Aber: Es ist überraschend leicht, nur die DB zu kompromittieren, ohne dabei Zugriff aufs File System zu erlangen. Genau das passiert ja bei SQL Injection Angriffen. Wenn natürlich der SQL-User auch noch die Möglichkeit hat, eine Form von Execute-Befehlen an die Shell zu senden, wars das...
Ich sagte doch, wenn es dich glücklich macht, dann kannst du auch einen globalen Salt und einen Salt pro User gleichzeitig nutzen, dann hast du das Beste aus beiden Welten.


Daaron schrieb:
Woran willst du da was erkennen? Der Hash ist ja immer noch md5($pw.$salt). Jedes PW erzeugt hier einen einzigartigen Hash (solange der Hash-Algorithmus kollisionsfrei ist)
Du erinnerst dich aber schon daran, dass Hashes deterministisch und nicht einzigartig sind? ;)
ein md5($a) wird für das gleiche $a somit immer den gleichen Hash erzeugen. Mit deinem globalen salt $salt hast du nun das Problem, dass 2 Personen mit gleichem Plaintext-Passwort $pw den gleichen String $a erzeugen und somit den gleichen Hash haben. Der Hash ist nicht einzigartig.
Hast du jedoch ein md5($pw.$userSalt.$globalSalt) ist jeder Hash einzigartig, da $a auf Grund von $userSalt immer einen anderen Wert haben wird.
Und bitte keine Strings verketten, das erlaubt Padding-Angriffe auf Hashing-Algorithmen, für soetwas wurde HMAC erfunden. Wenn ein Hashing-Algorithmus gebrochen ist, ist die HMAC-Variante noch immer sicher, da man erst den komplexen Fall herstellen muss, dass über 2 Hashes hinweg eine Kollision entsteht.


Daaron schrieb:
Wie gesagt: die einfachste Lösung ist doppeltes Hashing. Das hebelt z.B. schonmal alle RT-Ansätze mehr oder weniger aus. Oder hast du schonmal ne RT gesehen, in der die Hashes aller möglichen Hashes stehen? Das wird ne große RT....
du musst dich nicht auf Rainbowtables begrenzen, dann nimm Bruteforce, und den Bruteforce-Angriff stört das doppelte Hashing nicht, es macht die Laufzeit nur länger, wobei die Rainbowtable eben unnütz wird. Und bei 2Mrd md5-Hashes auf einer Grafikkarte pro Sekunde, sind auch hundert oder tausend Hash-Iterationen noch wenig. Und selbst wenn du den Wert erhöhst, in 5 Jahren steigt die Rechenleistung wieder so dass deine aktuell hoch gewählte Iterationszahl eventuell nicht mehr reicht und du hast sie nicht angepasst. Oder es wird gar ein Angriff gewonnen der speziell gegen iterierte md5-Aktionen gerichtet ist. Bei einem UserSalt ist das wieder ziemlich egal, denn der Aufwand für die ganze UserDatenbank ist dann noch immer astronomisch.
Achso, nur nebenbei du beschreibst gerade PBKDF2 bzw. Bcrypt ;) Welche beide auch intelligenter umgesetzt sind, nie eigene Crypto-Algorithmen erfinden, die haben meist Schwächen, i.R. gibt es fertiges bei denen sich Kryptologen schon einige Gedanken gemacht haben.
 
Zuletzt bearbeitet:
edit: an Daaron gerichtet

Warum nicht einfach wie ice-breaker sagte ein globales salt und ein individuelles? Der Aufwand dafür ist so minimal, dass sich eine Diskussion eigentlich nicht lohnt.

Aber aus Interesse eine andere Frage zu deiner Bemerkung oben: Bei 1-9 Zeichen eine RT mit 109GB - dort sind also alle Permutationen bis 9 Zeichen drin? Da stellt sich mir die Frage was dagegen spricht, dass man nicht bereits heute schon einfach salts mit 50-100 Zeichen oder gar mehr her nimmt. Aus ökonomischer Sicht eigentlich egal, da die etwas teurere Operation nur beim Login zum Tragen kommt und Speicherplatz heute ja auch nicht mehr wirklich was kostet.
 
Zuletzt bearbeitet:
Das Problem ist doch, wenn du einen Salt in die RT hineinnimmst, gilt das Passwort nur für diesen Salt.
Ein Beispiel:
Alle Passwörter mit den Zahlen von 0-9 und 4 Stellen sind insgesammt nur 10.000 Kombinationen, die kann man in eine RT speichern. Wenn du das mit dem Salt $aw3fhrt.$ machst, bleiben es noch immer nur 10.000 Kombinationen. Aber da jede Seite einen anderen Salt nehmen kann, musst du eben auch rechnen dass es weitere 50 bis 100 variable Zeichen sind, also die Kombinationen ins astronomische steigen ;)
Deshalb ist die RT wirklich nur sinnvoll, wenn man ungehashte Passwörter hat oder sichergehen kann, dass der gleiche Hash überall genutzt wird (weil eine Software diesen statisch und nicht pro Installation variabel gemacht hat).

RT ist also eine Sache die nur sehr sehr sehr selten bei unfähigen Entwicklern zum Tragen kommt (ungesalzene Passwörter), sobald das Passwort aber gesalzen ist, ist der benötigte Rechenaufwand bzw. Zeitkomplexität für den Bruteforce-Angriff auf eine Nutzerdatenbank von Relevanz.
 
Meinst du mich? :) Ja ist klar dass RTs nur eine Möglichkeit darstellen.

Wie erzeugt man eigentlich am besten einen individuellen Salt? In meiner Unkreativität würde ich wahrscheinlich lediglich ein Produkt aus irgendeinem Zufallsgenerator durch eine hash-Funktion schicken...
 
Die Qualität des Zufalls ist für einen Passwort-Salt eigentlich egal, hauptsache er ist lang genug um mögliche Kollisionen des Zufalls zu verhindern. Also einfach x Zuffalszeichen bestimmt durch mt_rand() ist da am sinnvollsten und einfachsten.
Du kannst natürlich auch /dev/random oder von OpenSSL die random bytes Funktion nutzen, beide sind aber von der Geschwindigkeit deutlich langsamer, besonders letzteres unter Windows. Einige mt_rand() Aufrufe und daraus einen String erstellen ist absolut ausreichend.
 
ice-breaker schrieb:
Ich sagte doch, wenn es dich glücklich macht, dann kannst du auch einen globalen Salt und einen Salt pro User gleichzeitig nutzen, dann hast du das Beste aus beiden Welten.
Das ist, wie ich finde, die einzig sinnige Lösung. Denn wenn du wirklich 1 Salt pro User nutzt, dann rechnest du im Endeffekt nur damit, dass für deine Salts keine RTs existieren. Das ist aber eher eine Zeitfrage.

Du erinnerst dich aber schon daran, dass Hashes deterministisch und nicht einzigartig sind? ;)
ein md5($a) wird für das gleiche $a somit immer den gleichen Hash erzeugen. Mit deinem globalen salt $salt hast du nun das Problem, dass 2 Personen mit gleichem Plaintext-Passwort $pw den gleichen String $a erzeugen und somit den gleichen Hash haben. Der Hash ist nicht einzigartig.
Das ist mir schon klar. Deshalb zwingt man seine User auch dazu, etwas sicherere PWs zu verwenden. So eine Passwortstärke-Anzeige auf JS-Basis macht beim Anlegen des Accounts schon teilweise viel aus. Wenn man ihm dann sein "rotes" PW auch noch um die Ohren haut und erst ab "gelb" überhaupt zu lässt, dann hat man schon ein Problem weniger.

du musst dich nicht auf Rainbowtables begrenzen, dann nimm Bruteforce, und den Bruteforce-Angriff stört das doppelte Hashing nicht, es macht die Laufzeit nur länger, wobei die Rainbowtable eben unnütz wird.
Hängt vom Grad der Kompromittierung ab. Du kannst z.B. mal versuchen ein PW zu bruteforce'n, dass nach 3 Fehlversuchen für 15Min geblockt wird... oder bei dem jeder PW-Check durch hirnlose Schleifen künstlich auf 500ms hochgedrückt wurde.

Für viele Angriffe in realistischer Zeit muss man davon ausgehen, dass Zugriff aufs Dateisystem, nicht nur auf die DB, ermöglicht wurde. Wenn es schon so weit ist, dann muss man Passwörter gar nicht mehr knacken und sich keinerlei Sorgen um Salts machen. Deine User kennen ihre Passwörter doch eh am besten und senden sie dir doch freiwillig. 2-3 Zeilen frischer Code in der Login-Funktion, schon hat man alle Credentials im eigenen Postfach, und im Zweifel hats nicht einmal jemand gemerkt.


dcobra schrieb:
Aber aus Interesse eine andere Frage zu deiner Bemerkung oben: Bei 1-9 Zeichen eine RT mit 109GB - dort sind also alle Permutationen bis 9 Zeichen drin?
In dieser immerhin RT sind nur Kleinbuchstaben, Zahlen sowie (so wie ich das verstanden hab) eben Leerzeichen. Pack ein % irgendwo in den String und er taucht nicht in diesen 109GB auf. Ich würde noch nicht einmal darauf wetten, dass diese 109GB auch unsere Umlaute umfassen, geschweige denn Accents, dieses lustige französische oe, dieses nordeuropäische durchgestrichene o, das spanische ñ,...
Wenn ich nicht total daneben liege, dann sind das 37^9 Kombinationen (26 Buchstaben, 10 Ziffern, Space). Das sind 1,299617398×10¹⁴ Kombinationen. Pack jetzt einfach nur EINEN Buchstaben mehr rein, eben z.B. é, schon werden daraus 1,652161013×10¹⁴ Kombinationen, bei gleicher Zeichenlänge, eine ~28%ige Steigerung des Aufwands durch EIN neues Zeichen.

Um diese RT auszuhebeln kannst du als User sogar dein Geburtsdatum als PW nehmen, du musst nur vorher Capslock anschalten. 8 Stellen, davon keines alphanumerisch.... ein simpler Substitutionschifre.

Um eine RT gezielt einzusetzen musst du diese RT auf dein Zielgebiet anpassen. Wenn deine User englischsprachig sind, dann kannst du getrost a-z A-Z 0-9 annehmen. Hast du einen Deutschen an der Backe, schon musst du um ßäöüÄÖÜ erweitern. Diese Zeichen hast du bei nem Franzosen wiederum nicht, dafür knallt der dir vielleicht alles mit Accents zu... und n Russen kriegst du so oder so nicht dran, der pumpt dir kyrillische Passwörter in die Datenbank. Der braucht n völlig neuen Ansatz.

Der effektivste Weg gegen Passwörter vorzugehen ist und bleibt Social Engineering. Knacken kostet Zeit, die Leute zur Preisgabe zu überreden erfordert nur Köpfchen.


Nachtrag zur Erzeugung von Salts:
Wie wärs mit "pwgen -1ys 50" ? natürlich vorausgesetzt, das Paket existiert im System...
Reproduzier mal dieses Salt: Pf&]wKb,~"\>Z:NTylcrn~!*rt~o1>e%7L`V@{,3)7j/N7qYS^
 
Zuletzt bearbeitet:
ice-breaker schrieb:
Und bitte keine Strings verketten, das erlaubt Padding-Angriffe auf Hashing-Algorithmen, für soetwas wurde HMAC erfunden. Wenn ein Hashing-Algorithmus gebrochen ist, ist die HMAC-Variante noch immer sicher, da man erst den komplexen Fall herstellen muss, dass über 2 Hashes hinweg eine Kollision entsteht.
Wieso Strings nicht verketten? Oder meintest du zwei Hashes nicht als Strings verketten?


ice-breaker schrieb:
Achso, nur nebenbei du beschreibst gerade PBKDF2 bzw. Bcrypt ;)
Wo kommt ihr denn her? Ich spreche eure Sprache nicht! :D

Daaron schrieb:
Das ist, wie ich finde, die einzig sinnige Lösung. Denn wenn du wirklich 1 Salt pro User nutzt, dann rechnest du im Endeffekt nur damit, dass für deine Salts keine RTs existieren. Das ist aber eher eine Zeitfrage.

und n Russen kriegst du so oder so nicht dran, der pumpt dir kyrillische Passwörter in die Datenbank.
Ich denke, dass ich ab sofort nur noch kyrillische-französisch-arabisch gemischte Passwörter verwende! :D

Dann kann man sich wohl darauf einigen, dass Pepper&Salt der Way-to-go ist? :)
 
Jo, mit P&S machst du nix falsch.

Aber wie gesagt: das basiert alles darauf, dass der Server nicht auf Dateiebene kompromittiert wurde, sondern bestenfalls die Datenbank. In dem Moment, wo ich die login.php manipulieren kann, ist jeder andere Schutz für mich völlig irrelevant. Und selbst wenn ich das nicht kann, kann ich immer noch versuchen (wie bei Computec Media geschehen) die Seite selbst zu verseuchen, um Trojaner auszuliefern. So komme ich auch an einige Passwörter.
 
Nunja, wenn ich nur ein Web-Hosting Paket gebucht habe und die Server-Administration dem Hoster überlasse, dann ist das immerhin ein Punkt, um den ich mir selbst keine Gedanken machen zu brauche, oder? Im Idealfall natürlich regelmäßig den Code reviewen, ob er sich vielleicht verändert hat. Aber sonst bleibt da ja nichts übrig, das man tun kann?
 
Wieso? Sobald der Angreifer irgendwie dein FTP-Passwort geknackt hat kann er allen möglichen Mumpitz anstellen. Dasselbe gilt, falls er Zugriff auf deine DB erhält (z.B. per Injection). Und spätestens wenn du auf einem PHP-Server via CGI läufst und dein Betreiber noch nicht gepatched hat bist du eh ins Knie gef*ckt. Ruf mal spaßeshalber auf deiner Domain "index.php?-s" auf.
 
Mit welcher Funktion kann man denn dann einen Salt erstellen, der nicht nur Ziffern enthält (wie mt_rand)?
 
eine Möglichkeit wäre ein System Call, um z.B. "pwgen -1ys 50" aufzurufen. Gegen so einen Salt stinkt alles ab. Setzt halt voraus, dass du system() benutzen darfst und dass pwgen installiert ist
 
Systemzugriffe werden bei einem normalen Hostingpaket nicht möglich sein, nehme ich an.

Ich hänge gerade sehr an der Umsetzung des erworbenen Wissens. Ich komme gerade gar nicht mehr klar! :D

Wie speichere und prüfe ich jetzt zum Beispiel mit crypt() ?
Wie kann ich direkt mit sha512 etc. verschlüsseln?
 
$deinhash = hash('sha512','deinklartext'); sollte klappen

kein Plan, was bei crypt() so geht. Da würd ich auch nur das Online-Handbuch durchforsten... ich verwende nach Möglichkeit Frameworks irgend einer Bauart, die dann eh ihre eigenen Krypto-Klassen mitbringen.
 

Ähnliche Themen

Zurück
Oben