如何使用PHP';s password\u散列以散列和验证密码

如何使用PHP';s password\u散列以散列和验证密码,php,salt,password-hash,php-password-hash,Php,Salt,Password Hash,Php Password Hash,最近,我一直在尝试在我在互联网上偶然发现的一个登录脚本上实现我自己的安全性。在努力学习如何编写自己的脚本为每个用户生成salt之后,我偶然发现了密码\u散列 根据我的理解(基于阅读),当您使用password\u hash时,行中已经生成了salt。这是真的吗 我的另一个问题是,吃两种盐不是很明智吗?一个直接在文件中,一个在数据库中?那样的话,如果有人在数据库中泄露了你的salt,你仍然直接在文件中保留了salt?我在这里读到储存盐从来都不是一个聪明的想法,但它总是让我困惑人们的意思是什么 是的

最近,我一直在尝试在我在互联网上偶然发现的一个登录脚本上实现我自己的安全性。在努力学习如何编写自己的脚本为每个用户生成salt之后,我偶然发现了
密码\u散列

根据我的理解(基于阅读),当您使用
password\u hash
时,行中已经生成了salt。这是真的吗


我的另一个问题是,吃两种盐不是很明智吗?一个直接在文件中,一个在数据库中?那样的话,如果有人在数据库中泄露了你的salt,你仍然直接在文件中保留了salt?我在这里读到储存盐从来都不是一个聪明的想法,但它总是让我困惑人们的意思是什么

是的,这是真的。为什么您对函数的php常见问题表示怀疑?:)

运行
password\u hash()
的结果有四个部分:

  • 使用的算法
  • 参数
  • 实际密码散列
  • 如你所见,散列是它的一部分


    当然,您可以增加一层安全性,但我真的认为在常规php应用程序中这是过分的。默认的bcrypt算法很好,可选的blowfish算法可以说更好。

    是的,如果您理解正确,函数password\u hash()将自行生成salt,并将其包含在结果哈希值中。将盐存储在数据库中是绝对正确的,即使已知,它也能完成它的工作

    // 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);
    
    您提到的第二种盐(存储在文件中的盐)实际上是胡椒粉或服务器端的钥匙。如果你在炒之前加上它(比如盐),那么你就加一个胡椒粉。不过有一种更好的方法,可以先计算散列,然后用服务器端密钥加密(双向)散列。这使您可以在必要时更改钥匙


    与盐不同,这把钥匙应该保密。人们经常混淆密码并试图隐藏密码,但最好让密码隐藏起来,并用密钥添加密码。

    使用
    密码\u散列
    是存储密码的推荐方法。不要将它们与数据库和文件分开

    假设我们有以下输入:

    $password = $_POST['password'];
    
    首先执行以下操作对密码进行哈希:

    $hashed_password = password_hash($password, PASSWORD_DEFAULT);
    
    // 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.
    
    然后查看输出:

    var_dump($hashed_password);
    
    正如你所看到的,它是散列的。(我想你已经完成了这些步骤)

    现在将此哈希密码存储在数据库中,确保密码列足够大,可以容纳哈希值(至少60个字符或更长)。当用户要求登录时,您可以通过以下操作在数据库中使用此哈希值检查密码输入:

    $hashed_password = password_hash($password, PASSWORD_DEFAULT);
    
    // 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.
    

    永远不要使用md5()来保护密码,即使使用了盐,也总是很危险的

    使用最新的哈希算法保护您的密码,如下所示

    <?php
    
    // Your original Password
    $password = '121@121';
    
    //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);
    

    我构建了一个我一直在使用的函数,用于密码验证和创建密码,例如将密码存储在MySQL数据库中。它使用随机生成的盐,这比使用静态盐更安全

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

    对于PHP密码函数内置的向后和向前兼容性,显然缺乏讨论。值得注意的是:

  • 向后兼容性:密码函数本质上是一个编写良好的包装器,与
    crypt()
    格式的散列具有内在的向后兼容性,即使它们使用过时和/或不安全的散列算法
  • 转发兼容性:在身份验证工作流中插入和添加一点逻辑,可以使您的哈希值与当前和未来的算法保持最新,并且可能不会对工作流进行任何更改。注意:任何与指定算法不匹配的字符串都将被标记为需要重新哈希,包括不兼容密码的哈希
  • 例如:


    最后,请注意,您只能在登录时重新加密用户的密码,您应该考虑“SunS陷陷”不安全的遗留散列来保护用户。我的意思是,在一定的宽限期后,您将删除所有不安全的[例如:裸MD5/SHA/其他弱]散列,并让用户依赖于应用程序的密码重置机制。

    类密码完整代码:

    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;
        }
    
    }
    
    类密码{
    公共函数{u构造(){}
    /**
    *使用指定的算法散列密码
    *
    *@param string$password要哈希的密码
    *@param int$algo要使用的算法(由密码*常量定义)
    *@param array$options算法要使用的选项
    *
    *@return string |将哈希密码设置为false,或在出现错误时设置为false。
    */
    函数密码\u散列($password,$algo,array$options=array()){
    如果(!function_存在('crypt')){
    触发器错误(“必须加载密码才能使密码散列正常工作”,E用户警告);
    返回null;
    }
    如果(!是字符串($password)){
    触发器\u错误(“密码\u哈希():密码必须是字符串”,E\u用户\u警告);
    返回null;
    }
    如果(!is_int($algo)){
    trigger_error(“password_hash()期望参数2很长,”.gettype($algo)。“给定”,E_USER_警告);
    返回null;
    }
    交换机($algo){
    案例密码\u BCRYPT:
    //请注意,这是一个C常量,但未向PHP公开,因此我们不在这里定义它。
    $cost=10;
    如果(isset($options['cost'])){
    $cost=$options['cost'];
    如果($cost<4 | |$cost>31){
    触发器错误(sprintf(“密码\u哈希():指定的bcrypt成本参数无效:%d”,$cost),E\u用户\u警告);
    返回null;
    }
    }
    //要生成的盐的长度
    $raw_salt_len=16;
    //长度要求
    
    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)
    
    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;
        }
    
    }