PHP의 password_hash를 사용하여 비밀번호를 해시하고 확인하는 방법

php salt password-hash php-password-hash


최근에 나는 인터넷에서 우연히 만났던 로그인 스크립트에서 내 자신의 보안을 구현하려고 노력했다. 각 사용자에게 솔트를 생성하기 위해 자체 스크립트를 작성하는 방법을 배우려고 애 쓰고 난 후, password_hash 를 우연히 발견했습니다 .

내가 이해 한 것 ( 이 페이지 의 읽은 내용을 기반으로 )에서 password_hash 를 사용할 때 이미 행에 salt가 생성됩니다 . 이것이 사실입니까?

내가 가진 또 다른 질문은 2 개의 소금을 먹는 것이 현명하지 않습니까? 하나는 파일에 있고 다른 하나는 DB에 있습니까? 그런 식으로 누군가가 DB에서 소금을 손상 시키면 여전히 파일에 소금이 있습니까? 나는 소금을 저장하는 것이 결코 현명한 아이디어가 아니라는 것을 읽었지만 사람들이 그 의미를 항상 혼란스럽게했습니다.




Answer 1 Akar


password_hash 를 사용 하는 것이 권장되는 비밀번호 저장 방법입니다. DB와 파일로 분리하지 마십시오.

다음과 같은 입력이 있다고 가정 해 봅시다.

$password = $_POST['password'];

개념을 이해하기 위해 입력을 확인하지는 않습니다.

먼저 다음을 수행하여 비밀번호를 해시하십시오.

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

그런 다음 출력을 참조하십시오.

var_dump($hashed_password);

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

이제이 hashed_password를 데이터베이스에 저장하여 비밀번호 열이 해시 값 (60 자 이상)을 보유 할 수있을만큼 커야합니다. 사용자가 로그인을 요청하면 다음을 수행하여 데이터베이스에서이 해시 값으로 비밀번호 입력을 확인하십시오.

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

공식 참조




Answer 2 martinstoeckli


예, 정확하게 이해했습니다. password_hash () 함수는 자체적으로 솔트를 생성하고 결과 해시 값에 포함시킵니다. 데이터베이스에 소금을 저장하는 것은 절대적으로 정확하며 알려진 경우에도 그 역할을 수행합니다.

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

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

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




Answer 3 Joel Hinz


그래 그건 사실이야. 함수에서 PHP FAQ를 의심하는 이유는 무엇입니까? :)

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

  1. 사용 된 알고리즘
  2. parameters
  3. salt
  4. 실제 비밀번호 해시

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

물론 추가 보안 계층에 추가 소금을 넣을 수는 있지만 정식 PHP 응용 프로그램에서는 과도하다고 생각합니다. 기본 bcrypt 알고리즘이 좋고 선택적인 복어 알고리즘이 더 좋습니다.




Answer 4 Mahesh Yadav


소금으로도 비밀번호를 보호하기 위해 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);

?>

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




Answer 5 Sammitch


PHP의 암호 함수에 내장 된 이전 버전과 이전 버전의 호환성에 대한 명확한 논의가 없습니다. 특히 :

  1. 이전 버전과의 호환성 : 비밀번호 함수는 본질적으로 crypt() 주위에 잘 작성된 래퍼 이며, crypt() 형식 해시 와는 본질적으로 이전 버전과 호환 되지 않습니다.
  2. 포워드 호환성 : password_needs_rehash() 및 약간의 논리를 인증 워크 플로우에 삽입 하면 워크 플로우에 대한 향후 변경이 없을 가능성이있는 현재 및 미래의 알고리즘으로 해시를 최신 상태로 유지할 수 있습니다. 참고 : 지정된 알고리즘과 일치하지 않는 문자열은 비 호환 호환 해시를 포함하여 리해시가 필요한 것으로 표시됩니다.

Eg:

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

Output:

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 / 기타 약한) 해시를 모두 제거하고 사용자가 응용 프로그램의 비밀번호 재설정 메커니즘에 의존하게합니다.