Wie kann ich die Passwörter meiner Benutzer sicher speichern?

169

Wie viel sicherer ist das als normales MD5 ? Ich habe gerade angefangen, mich mit der Passwortsicherheit zu befassen. Ich bin ziemlich neu in PHP.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}
Bewehrung
quelle
Hinweis PHP 5.4+ hat dies eingebaut
Benjamin Gruenbaum
Siehe auch Openwalls PHP Passass Hashing Framework (PHPass). Es ist tragbar und gegen eine Reihe gängiger Angriffe auf Benutzerkennwörter geschützt.
JWW
1
Obligatorisch "Verwenden Sie PDOs anstelle von String-Interpolation" für Menschen, die heute über diese Frage stolpern.
Fund Monica Klage

Antworten:

270

Der einfachste Weg, um Ihr Passwortspeicherschema sicher zu machen, ist die Verwendung einer Standardbibliothek .

Da die Sicherheit in der Regel viel komplizierter ist und unsichtbarere Möglichkeiten bietet, als die meisten Programmierer allein in Angriff nehmen könnten, ist die Verwendung einer Standardbibliothek fast immer die einfachste und sicherste (wenn nicht die einzige) verfügbare Option.


Die neue PHP-Passwort-API (5.5.0+)

Wenn Sie PHP Version 5.5.0 oder höher verwenden, können Sie die neue vereinfachte Passwort-Hashing-API verwenden

Beispiel für Code mit der PHP-Passwort-API:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(Falls Sie noch Legacy 5.3.7 oder höher verwenden, können Sie ircmaxell / password_compat installieren , um Zugriff auf die integrierten Funktionen zu erhalten.)


Verbesserung der gesalzenen Hashes: Pfeffer hinzufügen

Wenn Sie zusätzliche Sicherheit wünschen, empfehlen die Sicherheitsleute jetzt (2017) , den (automatisch) gesalzenen Passwort-Hashes einen " Pfeffer " hinzuzufügen .

Es gibt eine einfache Drop-in-Klasse, die dieses Muster sicher implementiert. Ich empfehle: Netsilik / PepperedPasswords ( github ).
Es wird mit einer MIT-Lizenz geliefert, sodass Sie es auch in proprietären Projekten verwenden können, wie Sie möchten.

Beispiel für Code mit Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


Die ALTE Standardbibliothek

Bitte beachten Sie : Sie sollten dies nicht mehr benötigen! Dies ist nur aus historischen Gründen hier.

Schauen Sie sich an: Portable PHP Password Hashing Framework : phpass und stellen Sie sicher, dass Sie den CRYPT_BLOWFISHAlgorithmus verwenden, wenn dies überhaupt möglich ist.

Beispiel für Code mit phpass (v0.2):

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass wurde in einigen bekannten Projekten implementiert:

  • phpBB3
  • WordPress 2.5+ sowie bbPress
  • die Drupal 7-Version (Modul verfügbar für Drupal 5 & 6)
  • Andere

Das Gute ist, dass Sie sich keine Gedanken über die Details machen müssen. Diese Details wurden von erfahrenen Personen programmiert und von vielen Leuten im Internet überprüft.

Weitere Informationen zu Kennwortspeicherschemata finden Sie in Jeffs Blogbeitrag: Sie speichern Kennwörter wahrscheinlich falsch

Was auch immer Sie tun, wenn Sie sich für den Ansatz " Ich mache es selbst, danke " entscheiden, verwenden Sie ihn nicht mehr MD5oder SHA1nicht mehr . Sie sind nette Hashing-Algorithmen, werden aber aus Sicherheitsgründen als defekt angesehen .

Derzeit ist die Verwendung von Krypta mit CRYPT_BLOWFISH die beste Vorgehensweise.
CRYPT_BLOWFISH in PHP ist eine Implementierung des Bcrypt-Hash. Bcrypt basiert auf der Blowfish-Blockverschlüsselung und nutzt das teure Schlüssel-Setup, um den Algorithmus zu verlangsamen.

Jacco
quelle
29

Ihre Benutzer sind viel sicherer, wenn Sie parametrisierte Abfragen verwenden, anstatt SQL-Anweisungen zu verketten. Und das Salz sollte für jeden Benutzer eindeutig sein und zusammen mit dem Passwort-Hash gespeichert werden.

Anton Gogolev
quelle
1
Es gibt einen guten Artikel über die Sicherheit in PHP bei Nettuts +, Passwort-Salting wird ebenfalls erwähnt. Vielleicht sollten Sie einen Blick darauf werfen: net.tutsplus.com/tutorials/php/…
Fábio Antunes
3
Die Nettuts + sind ein sehr schlechter Artikel, der als Modell verwendet werden kann - sie beinhalten die Verwendung von MD5, das selbst mit Salz sehr leicht brutal gezwungen werden kann. Verwenden Sie stattdessen einfach die PHPass-Bibliothek, die weitaus besser ist als jeder Code, den Sie auf einer Tutorial-Site finden, dh diese Antwort: stackoverflow.com/questions/1581610/…
RichVel
11

Ein besserer Weg wäre, dass jeder Benutzer ein einzigartiges Salz hat.

Der Vorteil eines Salzes besteht darin, dass es für einen Angreifer schwieriger ist, die MD5-Signatur jedes Wörterbuchworts vorab zu generieren. Wenn ein Angreifer jedoch erfährt, dass Sie ein festes Salz haben, kann er die MD5-Signatur jedes Wörterbuchworts vorab generieren, dem Ihr festes Salz vorangestellt ist.

Ein besserer Weg ist, dass jedes Mal, wenn ein Benutzer sein Kennwort ändert, Ihr System ein zufälliges Salz generiert und dieses Salz zusammen mit dem Benutzerdatensatz speichert. Es macht es etwas teurer, das Passwort zu überprüfen (da Sie das Salt nachschlagen müssen, bevor Sie die MD5-Signatur generieren können), aber es macht es für einen Angreifer viel schwieriger, MD5s vorab zu generieren.

R Samuel Klatchko
quelle
3
Salze werden normalerweise zusammen mit dem Passwort-Hash gespeichert (z. B. der Ausgabe der crypt()Funktion). Und da Sie den Passwort-Hash trotzdem abrufen müssen, wird die Verwendung eines benutzerspezifischen Salt das Verfahren nicht teurer. (Oder meintest du, ein neues zufälliges Salz zu erzeugen ist teuer? Ich glaube nicht wirklich.) Sonst +1.
Inshallah
Aus Sicherheitsgründen möchten Sie möglicherweise nur über gespeicherte Prozeduren Zugriff auf die Tabelle gewähren und verhindern, dass der Hash jemals zurückgegeben wird. Stattdessen übergibt der Client das, was er für den Hash hält, und erhält ein Erfolgs- oder Fehlerflag. Dies ermöglicht dem gespeicherten Prozess, den Versuch zu protokollieren, eine Sitzung zu erstellen usw.
Steven Sudit
@Inshallah - Wenn alle Benutzer das gleiche Salz haben, können Sie den Wörterbuchangriff, den Sie auf Benutzer1 verwenden, gegen Benutzer2 wiederverwenden. Wenn jedoch jeder Benutzer ein eindeutiges Salt hat, müssen Sie für jeden Benutzer, den Sie angreifen möchten, ein neues Wörterbuch erstellen.
R Samuel Klatchko
@R Samuel - genau deshalb habe ich Ihre Antwort abgelehnt, weil sie die Best-Practice-Strategie zur Vermeidung solcher Angriffe empfiehlt. Mein Kommentar sollte meine Verwirrung darüber zum Ausdruck bringen, was Sie zu den zusätzlichen Kosten für ein Salz pro Benutzer gesagt haben, die ich überhaupt nicht verstanden habe. (Da "Salze normalerweise zusammen mit dem Passwort-Hash gespeichert werden", sind alle zusätzlichen Speicher- und CPU-Anforderungen für ein Salz pro Benutzer so mikroskopisch, dass sie nicht einmal erwähnt werden müssen ...)
Inshallah
@Inshallah - Ich habe über den Fall nachgedacht, in dem Sie die Datenbank überprüfen lassen, ob das Hash-Passwort in Ordnung ist (dann haben Sie einen Datenbankabruf, um das Salt zu erhalten, und einen zweiten Datenbankzugriff, um das Hash-Passwort zu überprüfen). Sie haben Recht mit dem Fall, dass Sie das Salt / Hash-Passwort in einem einzigen Abruf herunterladen und dann den Vergleich auf dem Client durchführen. Entschuldigung für die Verwirrung.
R Samuel Klatchko
11

Mit PHP 5.5 (was ich beschreibe, ist auch für frühere Versionen verfügbar, siehe unten) um die Ecke möchte ich vorschlagen, die neue, integrierte Lösung zu verwenden: password_hash()und password_verify(). Es bietet verschiedene Optionen, um die erforderliche Kennwortsicherheit zu erreichen (z. B. durch Angabe eines "Kosten" -Parameters über das $optionsArray).

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

wird zurückkehren

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

Wie Sie vielleicht sehen, enthält die Zeichenfolge das Salz sowie die Kosten, die in den Optionen angegeben wurden. Es enthält auch den verwendeten Algorithmus.

Wenn Sie das Kennwort überprüfen (z. B. wenn sich der Benutzer anmeldet) und die kostenlose password_verify()Funktion verwenden, werden daher die erforderlichen Kryptoparameter aus dem Kennwort-Hash selbst extrahiert.

Wenn kein Salt angegeben wird, unterscheidet sich der generierte Passwort-Hash bei jedem Aufruf von, password_hash()da das Salt zufällig generiert wird. Daher schlägt der Vergleich eines vorherigen Hashs mit einem neu generierten fehl, selbst bei einem korrekten Passwort.

Die Überprüfung funktioniert folgendermaßen:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

Ich hoffe, dass die Bereitstellung dieser integrierten Funktionen bald eine bessere Kennwortsicherheit im Falle eines Datendiebstahls bietet, da dadurch weniger Gedanken aufgewendet werden müssen, die der Programmierer für eine ordnungsgemäße Implementierung benötigt.

Es gibt eine kleine Bibliothek (eine PHP-Datei), die Ihnen PHP 5.5 password_hashin PHP 5.3.7+ bietet: https://github.com/ircmaxell/password_compat

Akirk
quelle
2
In den meisten Fällen ist es besser, den Salzparameter wegzulassen. Die Funktion erstellt ein Salz aus der zufälligen Quelle des Betriebssystems. Es besteht nur eine sehr geringe Wahrscheinlichkeit, dass Sie selbst ein besseres Salz bereitstellen können.
Martinstoeckli
1
Das habe ich doch geschrieben, oder? "Wenn kein Salz angegeben ist, wird es zufällig generiert. Aus diesem Grund ist es vorzuziehen, kein Salz anzugeben"
Akirk
Die meisten Beispiele zeigen, wie beide Parameter hinzugefügt werden, auch wenn es nicht empfohlen wird, ein Salz hinzuzufügen. Ich frage mich also, warum? Und um ehrlich zu sein, habe ich nur den Kommentar hinter dem Code gelesen, nicht in der nächsten Zeile. Wäre es nicht besser, wenn das Beispiel zeigt, wie die Funktion am besten genutzt wird?
Martinstoeckli
Du hast recht, ich stimme zu. Ich habe meine Antwort entsprechend geändert und die Zeile auskommentiert. Danke
Akirk
Wie soll ich überprüfen, ob das gespeicherte Passwort und das eingegebene Passwort identisch sind? Ich verwende password_hash()und password_verifyegal welches Passwort (richtig oder nicht) ich verwendet habe, am Ende habe ich das richtige Passwort
Brownman Revival
0

Das ist okay für mich. Herr Atwood schrieb über die Stärke von MD5 gegen Regenbogentische , und im Grunde genommen sitzen Sie mit einem langen Salz wie diesem hübsch (obwohl einige zufällige Satzzeichen / Zahlen es verbessern könnten).

Sie können sich auch SHA-1 ansehen, das heutzutage immer beliebter zu werden scheint.

nickf
quelle
6
Der Hinweis am Ende von Herrn Atwoods Beitrag (in rot) verweist auf einen anderen Beitrag eines Sicherheitspraktikers, der angibt, dass die Verwendung von MD5, SHA1 und anderen schnellen Hashes zum Speichern von Passwörtern sehr falsch ist.
Sipwiz
2
@ Matthew Scharley: Ich stimme nicht zu, dass der zusätzliche Aufwand, der durch teure Passwort-Hashing-Algorithmen verursacht wird, falsche Sicherheit ist. Es soll verhindern, dass leicht zu erratende Passwörter brutal erzwungen werden. Wenn Sie Anmeldeversuche einschränken, schützen Sie sich vor derselben Sache (wenn auch etwas effektiver). Wenn ein Gegner jedoch Zugriff auf die in der Datenbank gespeicherten Hashes hat, kann er solche (leicht zu erratenden) Passwörter ziemlich schnell brutal erzwingen (je nachdem, wie leicht zu erraten ist). Der Standardwert für den SHA-256-Kryptoalgorithmus ist 10000 Round, was ihn 10000-mal schwieriger machen würde.
Inshallah
3
Die langsamen Hashes werden tatsächlich erstellt, indem ein schneller sehr oft wiederholt wird und die Daten zwischen den einzelnen Iterationen gemischt werden. Das Ziel ist es sicherzustellen, dass der Bösewicht, selbst wenn er eine Kopie Ihrer Passwort-Hashes erhält, eine beträchtliche Menge an CPU-Zeit verbrauchen muss, um sein Wörterbuch mit Ihren Hashes zu testen.
Café
4
@caf: Ich glaube, der bcrypt-Algorithmus nutzt die parametrierbare Kosten der Eksblowfish-Schlüsselplanung. Ich bin mir nicht ganz sicher, wie das funktioniert, aber die Schlüsselplanung ist oft eine sehr teure Operation, die während der Initiierung eines Verschlüsselungskontextobjekts durchgeführt wird, bevor eine Verschlüsselung durchgeführt wird.
Inshallah
3
Inshallah: Dies ist wahr - der bcrypt-Algorithmus ist ein anderes Design, bei dem das zugrunde liegende Krypto-Primitiv eher eine Blockverschlüsselung als eine Hash-Funktion ist. Ich bezog mich auf Schemata, die auf Hash-Funktionen basieren, wie PHKs MD5 crypt ().
Café
0

Ich will hinzufügen:

  • Beschränken Sie Benutzerpasswörter nicht nach Länge

Legen Sie aus Gründen der Kompatibilität mit alten Systemen häufig eine Grenze für die maximale Länge des Kennworts fest. Dies ist eine schlechte Sicherheitsrichtlinie: Wenn Sie eine Einschränkung festlegen, legen Sie diese nur für die Mindestlänge von Kennwörtern fest.

  • Senden Sie keine Benutzerkennwörter per E-Mail

Um ein vergessenes Passwort wiederherzustellen, sollten Sie die Adresse senden, unter der der Benutzer das Passwort ändern kann.

  • Aktualisieren Sie die Hashes der Benutzerkennwörter

Der Passwort-Hash ist möglicherweise veraltet (Parameter des Algorithmus werden möglicherweise aktualisiert). Mit der Funktion können password_needs_rehash()Sie es überprüfen.


quelle