wake-up-neo.com

Wie benutzt man password_hash?

Kürzlich habe ich versucht, meine eigene Sicherheit in einem Anmeldeskript zu implementieren, auf das ich im Internet gestoßen bin. Nachdem ich versucht hatte, mein eigenes Skript zu erstellen, um für jeden Benutzer ein Salt zu generieren, stieß ich auf password_hash.

Soweit ich weiß (basierend auf der Lektüre auf dieser Seite: http://php.net/manual/en/faq.passwords.php ), wird bei Verwendung bereits Salz in der Zeile erzeugt password_hash. Ist das wahr?

Eine andere Frage, die ich hatte, war, wäre es nicht klug, zwei Salze zu haben? Eine direkt in der Datei und eine in der DB? Auf diese Weise, wenn jemand Ihr Salz in der DB gefährdet, haben Sie immer noch die direkt in der Datei? Ich lese hier weiter, dass das Lagern von Salz nie eine kluge Idee ist, aber es hat mich immer verwirrt, was die Leute damit gemeint haben.

66
Josh Potter

Mit password_hash ist die empfohlene Methode zum Speichern von Kennwörtern. Trennen Sie sie nicht in DB und Dateien.

Nehmen wir an, wir haben folgenden Input:

$password = $_POST['password'];

Ich validiere die Eingabe nicht nur, um das Konzept zu verstehen.

Sie haben zuerst das Passwort gehasht, indem Sie dies tun:

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

Dann sehen Sie die Ausgabe:

var_dump($hashed_password);

Wie Sie sehen können, ist es gehasht. (Ich nehme an, Sie haben diese Schritte ausgeführt).

Jetzt speichern Sie dieses hashed_password in Ihrer Datenbank. Nehmen wir an, ein Benutzer möchte sich anmelden. So überprüfen Sie die Kennworteingabe mit diesem Hashwert in der Datenbank:

// Query the database for username and password
// ...

if(password_verify($password, $hashed_password)) {
    // If the password inputs matched the hashed password in the database
    // Do something, you know... log them in.
} 

// Else, Redirect them back to the login page.

offizielle Referenz

118
Akar

Ja, Sie haben es richtig verstanden. Die Funktion password_hash () generiert automatisch ein Salt und nimmt es in den resultierenden Hash-Wert auf. Das Speichern des Salzes in der Datenbank ist absolut korrekt, es funktioniert auch, wenn es bekannt ist.

// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($_POST['password'], PASSWORD_DEFAULT);

// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($_POST['password'], $existingHashFromDb);

Das zweite Salz, das Sie erwähnt haben (dasjenige, das in einer Datei gespeichert ist), ist tatsächlich ein Pfeffer- oder ein serverseitiger Schlüssel. Wenn Sie es vor dem Haschieren hinzufügen (wie das Salz), dann fügen Sie einen Pfeffer hinzu. Es gibt jedoch eine bessere Möglichkeit, zuerst den Hash zu berechnen und anschließend den Hash mit einem serverseitigen Schlüssel zu verschlüsseln (in beide Richtungen). Dies gibt Ihnen die Möglichkeit, den Schlüssel bei Bedarf zu ändern.

Im Gegensatz zum Salz sollte dieser Schlüssel geheim gehalten werden. Die Leute mischen es oft und versuchen, das Salz zu verbergen, aber es ist besser, das Salz seinen Job machen zu lassen und das Geheimnis mit einem Schlüssel hinzuzufügen.

19
martinstoeckli

Ja, es ist wahr. Warum bezweifeln Sie die PHP-FAQ über die Funktion? :)

Das Ergebnis von password_hash() besteht aus vier Teilen:

  1. der verwendete Algorithmus
  2. parameter
  3. salz-
  4. aktueller Passwort-Hash

Wie Sie sehen, ist der Hash ein Teil davon.

Sicher, Sie könnten ein zusätzliches Salz für eine zusätzliche Sicherheitsebene haben, aber ich denke ehrlich, das ist übertrieben in einer regulären PHP-Anwendung. Der Standard-Verschlüsselungsalgorithmus ist gut, und der optionale Blowfish-Algorithmus ist wahrscheinlich sogar noch besser.

5
Joel Hinz

Es gibt einen deutlichen Mangel an Diskussionen über die Rückwärts- und Vorwärtskompatibilität, die in die Passwortfunktionen von PHP eingebaut ist. Vor allem:

  1. Abwärtskompatibilität: Die Passwortfunktionen sind im Wesentlichen ein gut geschriebener Wrapper um crypt() und von Natur aus Abwärtskompatibel mit Hashes im crypt()-Format, auch wenn sie veraltete und/oder unsichere Hash-Algorithmen verwenden.
  2. Vorwärtskompatibilität: Das Einfügen von password_needs_rehash() und ein bisschen Logik in Ihren Authentifizierungsworkflow kann dazu führen, dass Sie Ihre Hashes behalten Auf dem neuesten Stand der aktuellen und zukünftigen Algorithmen mit potenziell null zukünftigen Änderungen am Workflow. Hinweis: Alle Zeichenfolgen, die nicht dem angegebenen Algorithmus entsprechen, müssen erneut verarbeitet werden, einschließlich nicht mit Crypt kompatibler Hashes.

Z.B:

class FakeDB {
    public function __call($name, $args) {
        printf("%s::%s(%s)\n", __CLASS__, $name, json_encode($args));
        return $this;
    }
}

class MyAuth {
    protected $dbh;
    protected $fakeUsers = [
        // old crypt-md5 format
        1 => ['password' => '$1$AVbfJOzY$oIHHCHlD76Aw1xmjfTpm5.'],
        // old salted md5 format
        2 => ['password' => '3858f62230ac3c915f300c664312c63f', 'salt' => 'bar'],
        // current bcrypt format
        3 => ['password' => '$2y$10$3eUn9Rnf04DR.aj8R3WbHuBO9EdoceH9uKf6vMiD7tz766rMNOyTO']
    ];

    public function __construct($dbh) {
        $this->dbh = $dbh;
    }

    protected function getuser($id) {
        // just pretend these are coming from the DB
        return $this->fakeUsers[$id];
    }

    public function authUser($id, $password) {
        $userInfo = $this->getUser($id);

        // Do you have old, turbo-legacy, non-crypt hashes?
        if( strpos( $userInfo['password'], '$' ) !== 0 ) {
            printf("%s::legacy_hash\n", __METHOD__);
            $res = $userInfo['password'] === md5($password . $userInfo['salt']);
        } else {
            printf("%s::password_verify\n", __METHOD__);
            $res = password_verify($password, $userInfo['password']);
        }

        // once we've passed validation we can check if the hash needs updating.
        if( $res && password_needs_rehash($userInfo['password'], PASSWORD_DEFAULT) ) {
            printf("%s::rehash\n", __METHOD__);
            $stmt = $this->dbh->prepare('UPDATE users SET pass = ? WHERE user_id = ?');
            $stmt->execute([password_hash($password, PASSWORD_DEFAULT), $id]);
        }

        return $res;
    }
}

$auth = new MyAuth(new FakeDB());

for( $i=1; $i<=3; $i++) {
    var_dump($auth->authuser($i, 'foo'));
    echo PHP_EOL;
}

Ausgabe:

MyAuth::authUser::password_verify
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$zNjPwqQX\/RxjHiwkeUEzwOpkucNw49yN4jjiRY70viZpAx5x69kv.",1]])
bool(true)

MyAuth::authUser::legacy_hash
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$VRTu4pgIkGUvilTDRTXYeOQSEYqe2GjsPoWvDUeYdV2x\/\/StjZYHu",2]])
bool(true)

MyAuth::authUser::password_verify
bool(true)

Da Sie das Passwort eines Benutzers nur bei der Anmeldung erneut prüfen können, sollten Sie abschließend unsichere Legacy-Hashes in Betracht ziehen, um Ihre Benutzer zu schützen. Damit meine ich, dass Sie nach einer gewissen Nachfrist alle unsicheren Hashes (z. B. MD5/SHA/ansonsten schwach) entfernen und Ihre Benutzer sich auf die Passwortrücksetzmechanismen Ihrer Anwendung verlassen müssen.

4
Sammitch

Verwenden Sie niemals md5 (), um Ihr Passwort zu sichern, auch nicht mit Salz, es ist immer gefährlich !!

Stellen Sie Ihr Passwort mit den neuesten Hashing-Algorithmen wie folgt sicher.

<?php

// Your original Password
$password = '[email protected]';

//PASSWORD_BCRYPT or PASSWORD_DEFAULT use any in the 2nd parameter
/*
PASSWORD_BCRYPT always results 60 characters long string.
PASSWORD_DEFAULT capacity is beyond 60 characters
*/
$password_encrypted = password_hash($password, PASSWORD_BCRYPT);

?>

Verwenden Sie die folgende Funktion, um eine Übereinstimmung mit dem verschlüsselten Kennwort der Datenbank und dem vom Benutzer eingegebenen Kennwort herzustellen.

<?php 

if (password_verify($password_inputted_by_user, $password_encrypted)) {
    // Success!
    echo 'Password Matches';
}else {
    // Invalid credentials
    echo 'Password Mismatch';
}

?>

Wenn Sie Ihr eigenes Salt verwenden möchten, verwenden Sie Ihre benutzerdefinierte generierte Funktion für dasselbe, folgen Sie einfach unten, aber ich empfehle dies nicht, da es in den neuesten PHP-Versionen als veraltet befunden wird.

lesen Sie diese http://php.net/manual/en/function.password-hash.php vor der Verwendung unter Code.

<?php

$options = [
    'salt' => your_custom_function_for_salt(), 
    //write your own code to generate a suitable & secured salt
    'cost' => 12 // the default cost is 10
];

$hash = password_hash($your_password, PASSWORD_DEFAULT, $options);

?>

Hoffe das alles hilft !!

4
Mahesh Yadav

Klasse Passwort vollständiger Code:

Class Password {

    public function __construct() {}


    /**
     * Hash the password using the specified algorithm
     *
     * @param string $password The password to hash
     * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
     * @param array  $options  The options for the algorithm to use
     *
     * @return string|false The hashed password, or false on error.
     */
    function password_hash($password, $algo, array $options = array()) {
        if (!function_exists('crypt')) {
            trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
            return null;
        }
        if (!is_string($password)) {
            trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
            return null;
        }
        if (!is_int($algo)) {
            trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
            return null;
        }
        switch ($algo) {
            case PASSWORD_BCRYPT :
                // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
                $cost = 10;
                if (isset($options['cost'])) {
                    $cost = $options['cost'];
                    if ($cost < 4 || $cost > 31) {
                        trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
                        return null;
                    }
                }
                // The length of salt to generate
                $raw_salt_len = 16;
                // The length required in the final serialization
                $required_salt_len = 22;
                $hash_format = sprintf("$2y$%02d$", $cost);
                break;
            default :
                trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
                return null;
        }
        if (isset($options['salt'])) {
            switch (gettype($options['salt'])) {
                case 'NULL' :
                case 'boolean' :
                case 'integer' :
                case 'double' :
                case 'string' :
                    $salt = (string)$options['salt'];
                    break;
                case 'object' :
                    if (method_exists($options['salt'], '__tostring')) {
                        $salt = (string)$options['salt'];
                        break;
                    }
                case 'array' :
                case 'resource' :
                default :
                    trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
                    return null;
            }
            if (strlen($salt) < $required_salt_len) {
                trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
                return null;
            } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
                $salt = str_replace('+', '.', base64_encode($salt));
            }
        } else {
            $salt = str_replace('+', '.', base64_encode($this->generate_entropy($required_salt_len)));
        }
        $salt = substr($salt, 0, $required_salt_len);

        $hash = $hash_format . $salt;

        $ret = crypt($password, $hash);

        if (!is_string($ret) || strlen($ret) <= 13) {
            return false;
        }

        return $ret;
    }


    /**
     * Generates Entropy using the safest available method, falling back to less preferred methods depending on support
     *
     * @param int $bytes
     *
     * @return string Returns raw bytes
     */
    function generate_entropy($bytes){
        $buffer = '';
        $buffer_valid = false;
        if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
            $buffer = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
            if ($buffer) {
                $buffer_valid = true;
            }
        }
        if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
            $buffer = openssl_random_pseudo_bytes($bytes);
            if ($buffer) {
                $buffer_valid = true;
            }
        }
        if (!$buffer_valid && is_readable('/dev/urandom')) {
            $f = fopen('/dev/urandom', 'r');
            $read = strlen($buffer);
            while ($read < $bytes) {
                $buffer .= fread($f, $bytes - $read);
                $read = strlen($buffer);
            }
            fclose($f);
            if ($read >= $bytes) {
                $buffer_valid = true;
            }
        }
        if (!$buffer_valid || strlen($buffer) < $bytes) {
            $bl = strlen($buffer);
            for ($i = 0; $i < $bytes; $i++) {
                if ($i < $bl) {
                    $buffer[$i] = $buffer[$i] ^ chr(mt_Rand(0, 255));
                } else {
                    $buffer .= chr(mt_Rand(0, 255));
                }
            }
        }
        return $buffer;
    }

    /**
     * Get information about the password hash. Returns an array of the information
     * that was used to generate the password hash.
     *
     * array(
     *    'algo' => 1,
     *    'algoName' => 'bcrypt',
     *    'options' => array(
     *        'cost' => 10,
     *    ),
     * )
     *
     * @param string $hash The password hash to extract info from
     *
     * @return array The array of information about the hash.
     */
    function password_get_info($hash) {
        $return = array('algo' => 0, 'algoName' => 'unknown', 'options' => array(), );
        if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
            $return['algo'] = PASSWORD_BCRYPT;
            $return['algoName'] = 'bcrypt';
            list($cost) = sscanf($hash, "$2y$%d$");
            $return['options']['cost'] = $cost;
        }
        return $return;
    }

    /**
     * Determine if the password hash needs to be rehashed according to the options provided
     *
     * If the answer is true, after validating the password using password_verify, rehash it.
     *
     * @param string $hash    The hash to test
     * @param int    $algo    The algorithm used for new password hashes
     * @param array  $options The options array passed to password_hash
     *
     * @return boolean True if the password needs to be rehashed.
     */
    function password_needs_rehash($hash, $algo, array $options = array()) {
        $info = password_get_info($hash);
        if ($info['algo'] != $algo) {
            return true;
        }
        switch ($algo) {
            case PASSWORD_BCRYPT :
                $cost = isset($options['cost']) ? $options['cost'] : 10;
                if ($cost != $info['options']['cost']) {
                    return true;
                }
                break;
        }
        return false;
    }

    /**
     * Verify a password against a hash using a timing attack resistant approach
     *
     * @param string $password The password to verify
     * @param string $hash     The hash to verify against
     *
     * @return boolean If the password matches the hash
     */
    public function password_verify($password, $hash) {
        if (!function_exists('crypt')) {
            trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
            return false;
        }
        $ret = crypt($password, $hash);
        if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
            return false;
        }

        $status = 0;
        for ($i = 0; $i < strlen($ret); $i++) {
            $status |= (ord($ret[$i]) ^ ord($hash[$i]));
        }

        return $status === 0;
    }

}
1