Wie man passwort_hash von PHP verwendet, um Passwörter zu hashen und zu verifizieren

Lesezeit: 12 Minuten

Wie man passwort hash von PHP verwendet um Passworter zu hashen
Josh Potter

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, zu lernen, wie ich mein eigenes Skript erstellen kann, um ein Salz für jeden Benutzer zu generieren, stieß ich auf password_hash.

Soweit ich weiß (basierend auf der Lektüre auf diese Seite), Salz wird bereits in der Zeile generiert, wenn Sie verwenden password_hash. Ist das wahr?

Eine andere Frage, die ich hatte, war, wäre es nicht klug, 2 Salze zu haben? Eine direkt in der Datei und eine in der DB? Auf diese Weise haben Sie, wenn jemand Ihr Salz in der DB kompromittiert, das Salz immer noch direkt in der Datei? Ich habe hier gelesen, dass die Aufbewahrung von Salzen nie eine schlaue Idee ist, aber es hat mich immer verwirrt, was die Leute damit gemeint haben.

  • Nein. Lassen Sie die Funktion sich um das Salz kümmern. Doppeltes Salzen wird Ihnen Probleme bereiten und ist nicht nötig.

    – Funk Forty-Niner

    16. Mai 2015 um 18:43 Uhr

  • Wie @martinstoeckli in seiner Antwort erwähnt, ist das, was hier beschrieben wird, als “Pfeffer” bekannt und wird heutzutage oft empfohlen. Du warst ein Vorreiter, Josh! 😀

    – JoLoCo

    3. Februar um 17:55 Uhr

Wie man passwort hash von PHP verwendet um Passworter zu hashen
Akar

Verwenden password_hash ist die empfohlene Methode zum Speichern von Passwörtern. Trennen Sie sie nicht von DB und Dateien.

Nehmen wir an, wir haben die folgende Eingabe:

$password = $_POST['password'];

Sie hashen zuerst das Passwort, indem Sie Folgendes 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 gehashte Passwort in Ihrer Datenbank, Stellen Sie sicher, dass Ihre Passwortspalte groß genug ist, um den Hash-Wert aufzunehmen (mindestens 60 Zeichen oder länger).. Wenn ein Benutzer ihn anmeldet, überprüfen Sie die Passworteingabe mit diesem Hashwert in der Datenbank, indem Sie Folgendes tun:

// 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

  • Ok, ich habe es gerade ausprobiert und es hat funktioniert. Ich zweifelte an der Funktion, weil es fast zu einfach schien. Wie lange empfehlen Sie mir, die Länge meines Varchar zu machen? 225?

    – Josh Potter

    16. Mai 2015 um 19:11 Uhr


  • Das steht schon in den Handbüchern php.net/manual/en/function.password-hash.phpphp.net/manual/en/function.password-verify.php dass das OP wahrscheinlich nicht gelesen oder verstanden hat. Diese Frage wurde öfter als keine gestellt.

    – Funk Forty-Niner

    16. Mai 2015 um 19:16 Uhr

  • @FunkFortyNiner, b/c Josh hat die Frage gestellt, ich habe sie 2 Jahre später gefunden und sie hat mir geholfen. Das ist der Sinn von SO. Das Handbuch ist ungefähr so ​​​​klar wie Schlamm.

    – Todmo

    23. April 2018 um 1:22 Uhr

  • Was die Länge betrifft, so gibt es im PHP-Handbuch zu password_hash einen Kommentar in einem Beispiel: „Beachten Sie, dass sich DEFAULT im Laufe der Zeit ändern kann, also sollten Sie sich darauf vorbereiten, indem Sie Ihren Speicher auf über 60 Zeichen erweitern lassen (255 wären gut). “

    – Scheimus

    6. Juni 2020 um 3:37 Uhr

  • @toddmo : Um Ihren Kommentar zu untermauern, ich bin gerade im Juni 2020 auf diese Frage gestoßen, und die Diskussion hat mir Stunden der Frustration erspart. Auch ich finde das PHP-Handbuch meistens so klar wie Matsch.

    – Thomas Murphy

    10. Juni 2020 um 12:25 Uhr

1647093850 900 Wie man passwort hash von PHP verwendet um Passworter zu hashen
martinstöckli

Ja, Sie haben es richtig verstanden, die Funktion password_hash() generiert selbst ein Salt und fügt es in den resultierenden Hash-Wert ein. Das Speichern des Salzes in der Datenbank ist absolut korrekt, es erfüllt seinen Zweck, 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 von Ihnen erwähnte Salz (das in einer Datei gespeichert ist) ist eigentlich ein Pfeffer oder ein serverseitiger Schlüssel. Wenn Sie es vor dem Haschisch hinzufügen (wie das Salz), dann fügen Sie einen Pfeffer hinzu. Es gibt jedoch einen besseren Weg, Sie könnten zuerst den Hash berechnen und anschließend den Hash (in beide Richtungen) mit einem serverseitigen Schlüssel verschlüsseln. 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 verwechseln es oft und versuchen, das Salz zu verstecken, aber es ist besser, das Salz seine Arbeit machen zu lassen und das Geheimnis mit einem Schlüssel hinzuzufügen.

Ja, es ist wahr. Warum bezweifeln Sie die PHP-FAQ an der Funktion? 🙂

Das Ergebnis des Laufens password_hash() hat besteht aus vier Teilen:

  1. der verwendete Algorithmus
  2. Parameter
  3. Salz
  4. tatsächlicher Passwort-Hash

Wie Sie sehen können, ist das Hash ein Teil davon.

Sicher, Sie könnten ein zusätzliches Salz für eine zusätzliche Sicherheitsebene haben, aber ich denke ehrlich gesagt, dass das in einer normalen PHP-Anwendung übertrieben ist. Der Standard-bcrypt-Algorithmus ist gut, und der optionale Blowfish-Algorithmus ist wohl noch besser.

  • BCrypt ist ein hashen Funktion, während Blowfish ein Algorithmus für ist Verschlüsselung. BCrypt stammt jedoch aus dem Blowfish-Algorithmus.

    – Martinstöckli

    16. Mai 2015 um 18:43 Uhr

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 crypt()und sind von Natur aus abwärtskompatibel mit crypt()-Format-Hashes, auch wenn sie veraltete und/oder unsichere Hash-Algorithmen verwenden.
  2. Aufwärtskompatibilität: Einfügen password_needs_rehash() und ein bisschen Logik in Ihrem Authentifizierungs-Workflow kann Sie Ihre Hashes mit aktuellen und zukünftigen Algorithmen auf dem Laufenden halten, ohne zukünftige Änderungen am Workflow. Hinweis: Alle Zeichenfolgen, die nicht mit dem angegebenen Algorithmus übereinstimmen, werden gekennzeichnet, weil sie erneut gehasht werden müssen, einschließlich nicht mit Krypten 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)

Als letzte Anmerkung, da Sie das Passwort eines Benutzers nur bei der Anmeldung erneut hashen können, sollten Sie unsichere Legacy-Hashes zum “Sonnenuntergang” in Betracht ziehen, um Ihre Benutzer zu schützen. Damit meine ich, dass Sie nach einer gewissen Schonfrist alle Unsicherheiten entfernen [eg: bare MD5/SHA/otherwise weak] Hashes und lassen Sie Ihre Benutzer sich auf die Mechanismen zum Zurücksetzen von Passwörtern Ihrer Anwendung verlassen.

Wie man passwort hash von PHP verwendet um Passworter zu hashen
Dimitris Maniatis

Vollständiger Code des Klassenpassworts:

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;
    }

}

1647093851 228 Wie man passwort hash von PHP verwendet um Passworter zu hashen
TRIG

Ich habe eine Funktion gebaut, die ich ständig zur Passwortvalidierung und zum Erstellen von Passwörtern verwende, zB um sie in einer MySQL-Datenbank zu speichern. Es verwendet ein zufällig generiertes Salz, das viel sicherer ist als die Verwendung eines statischen Salzes.

function secure_password($user_pwd, $multi) {

/*
    secure_password ( string $user_pwd, boolean/string $multi ) 

    *** Description: 
        This function verifies a password against a (database-) stored password's hash or
        returns $hash for a given password if $multi is set to either true or false

    *** Examples:
        // To check a password against its hash
        if(secure_password($user_password, $row['user_password'])) {
            login_function();
        } 
        // To create a password-hash
        $my_password = 'uber_sEcUrE_pass';
        $hash = secure_password($my_password, true);
        echo $hash;
*/

// Set options for encryption and build unique random hash
$crypt_options = ['cost' => 11, 'salt' => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM)];
$hash = password_hash($user_pwd, PASSWORD_BCRYPT, $crypt_options);

// If $multi is not boolean check password and return validation state true/false
if($multi!==true && $multi!==false) {
    if (password_verify($user_pwd, $table_pwd = $multi)) {
        return true; // valid password
    } else {
        return false; // invalid password
    }
// If $multi is boolean return $hash
} else return $hash;

}

993840cookie-checkWie man passwort_hash von PHP verwendet, um Passwörter zu hashen und zu verifizieren

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy