복붙노트

password_hash 사용법

PHP

password_hash 사용법

최근에 나는 인터넷에서 우연히 만났던 스크립트의 로그에 내 자신의 보안을 구현하려고 노력 해왔다. 각 사용자별로 소금을 생성하는 스크립트를 만드는 방법을 배우려는 노력을 기울인 후에 나는 password_hash를 발견했습니다.

내가 이해하는 바에 따르면 (이 페이지의 읽기 : http://php.net/manual/en/faq.passwords.php), password_hash를 사용할 때 소금이 이미 행에 생성되어 있습니다. 사실입니까?

내가 가지고있는 또 다른 질문은 소금 2 개를 갖는 것이 현명하지 않겠는가? 하나는 파일에 직접, 다른 하나는 DB에 있습니까? 그렇게하면 누군가가 DB에서 소금을 손상 시키면 파일에 직접 소금을 보관할 수 있습니까? 나는 소금을 저장하는 것이 현명한 생각이 아니라는 것을 여기서 읽었지 만 사람들이 그것을 의미하는 것을 항상 혼란스럽게했다.

해결법

  1. ==============================

    1.password_hash를 사용하여 암호를 저장하는 것이 좋습니다. DB와 파일로 분리하지 마십시오.

    password_hash를 사용하여 암호를 저장하는 것이 좋습니다. DB와 파일로 분리하지 마십시오.

    우리가 다음과 같은 의견을 가지고 있다고 가정 해 봅시다 :

    $password = $_POST['password'];
    

    나는 그 개념을 이해하기 위해서 입력을 검증하지 않는다.

    다음을 수행하여 암호를 먼저 해싱합니다.

    $hashed_password = password_hash($password, PASSWORD_DEFAULT);
    

    그런 다음 결과를 확인하십시오.

    var_dump($hashed_password);
    

    보시다시피 해시입니다. (나는 당신이 그 단계를했다고 가정합니다).

    이제 데이터베이스에 hashed_password를 저장하고 사용자가 로그인하라는 메시지를 보자. 데이터베이스에서이 해시 값을 사용하여 비밀번호 입력을 확인한 후 다음 작업을 수행합니다.

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

    공식 참조

  2. ==============================

    2.네, 올바르게 이해하고 있습니다. password_hash () 함수는 자체적으로 salt를 생성하고 해시 값에 결과를 포함시킵니다. 소금을 데이터베이스에 저장하는 것은 절대적으로 정확합니다. 알려진 경우에도 작업을 수행합니다.

    네, 올바르게 이해하고 있습니다. password_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);
    

    언급 한 두 번째 소금 (파일에 저장된 소금)은 실제로 후추 또는 서버 측 키입니다. 해시 (소금처럼) 전에 추가하면 고추를 추가합니다. 더 좋은 방법이 있지만 처음에는 해시를 계산 한 다음 서버 측 키로 해시를 암호화 (양방향) 할 수 있습니다. 필요한 경우 키를 변경할 수 있습니다.

    소금과 달리이 열쇠는 비밀로 유지되어야합니다. 사람들은 종종 그것을 섞어서 소금을 숨기려하지만, 소금이 그 일을하고 열쇠로 비밀을 추가하는 것이 낫습니다.

  3. ==============================

    3.그래 그건 사실이야. 왜 함수에 대한 php faq를 의심합니까? :)

    그래 그건 사실이야. 왜 함수에 대한 php faq를 의심합니까? :)

    password_hash ()를 실행 한 결과는 네 부분으로 구성됩니다.

    보시다시피 해시는 그 일부입니다.

    물론, 추가 된 보안 계층을 위해 추가 소금을 가질 수는 있지만, 솔직히 그것이 일반 PHP 응용 프로그램에서 과도하다고 생각합니다. 기본 bcrypt 알고리즘은 좋으며 선택 사항 인 복어 1은 틀림없이 훨씬 좋습니다.

  4. ==============================

    4.암호로 보안 설정을 위해 md5 ()를 사용하지 마십시오. 소금으로도 위험합니다!

    암호로 보안 설정을 위해 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);
    
    ?>
    

    데이터베이스의 암호화 된 암호 및 사용자가 입력 한 암호와 일치 시키려면 아래 기능을 사용하십시오.

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

    자신 만의 소금을 사용하고 싶다면 다음과 같이 사용자 정의 생성 함수를 사용하십시오. 그러나 최신 버전의 PHP에서는 더 이상 사용되지 않습니다.

    이 코드를 사용하기 전에 http://php.net/manual/en/function.password-hash.php를 읽으십시오.

    <?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);
    
    ?>
    

    이 모든 것이 도움이되기를 바랍니다 !!

  5. ==============================

    5.PHP의 패스워드 함수에 내장 된 역방향 및 포워드 호환성에 대한 논의가 뚜렷하지 않습니다. 특히 :

    PHP의 패스워드 함수에 내장 된 역방향 및 포워드 호환성에 대한 논의가 뚜렷하지 않습니다. 특히 :

    예 :

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

    산출:

    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)
    

    마지막으로, 로그인 할 때 사용자의 암호 만 다시 해시 할 수 있다고 가정 할 때 사용자를 보호하기 위해 안전하지 않은 기존 해시를 "중지"해야합니다. 이 말은 특정 유예 기간이 지나면 모든 안전하지 않은 (예 : MD5 / SHA / weak 약한) 해시를 제거하고 사용자가 응용 프로그램의 암호 재설정 메커니즘에 의존하게합니다.

  6. ==============================

    6.클래스 암호 전체 코드 :

    클래스 암호 전체 코드 :

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

    7.암호 유효성 검사와 암호 생성을 위해 항상 사용하는 함수를 만들었습니다. 그들을 MySQL 데이터베이스에 저장합니다. 그것은 무작위로 생성 된 소금을 사용합니다. 이것은 정적 소금을 사용하는 것보다 훨씬 안전합니다.

    암호 유효성 검사와 암호 생성을 위해 항상 사용하는 함수를 만들었습니다. 그들을 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;
    
    }
    
  8. from https://stackoverflow.com/questions/30279321/how-to-use-password-hash by cc-by-sa and MIT license