PHP の password_hash を使ってパスワードをハッシュ化して検証する方法

php salt password-hash php-password-hash


最近、インターネットで偶然見つけたログインスクリプトに自分のセキュリティを実装しようとしています。ユーザーごとにソルトを生成する独自のスクリプトを作成する方法を学ぶのに苦労した後、私は password_hash を偶然見つけました。

私が理解していること(このページの読み取りに基づいて)から、 password_hash を使用すると、ソルトはすでに行に生成されています。これは本当ですか?

もう一つの質問は、2つの塩を持つのはスマートではないでしょうか?一つはファイルに直接、もう一つはDBに?そうすれば、誰かがDBにあるソルトを危うくしても、ファイルに直接あるソルトを使うことができるのではないでしょうか? ソルトを保存するのは決して賢い考えではないとここで読みましたが、それが何を意味しているのかいつも混乱してしまいます。




Answer 1 Akar


password_hash を保存するには、password_hashの使用が推奨されます。それらをDBとファイルに分離しないでください。

以下のような入力があったとします。

$password = $_POST['password'];

概念を理解するためだけに入力を検証しているわけではありません。

最初にパスワードをハッシュ化するには、このようにします。

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

続いて出力を見てください。

var_dump($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);

2つ目に挙げたソルト(ファイルに保存されているもの)は、実はコショウかサーバーサイドキーです。ハッシュの前に追加する場合は(ソルトのように)、コショウを追加することになります。最初にハッシュを計算してから、サーバー側の鍵でハッシュを暗号化(双方向)するという方法もあります。これにより、必要に応じて鍵を変更できるようになります。

塩とは対照的に、この鍵は秘密にしておくべきものです。人はよく混ぜて塩を隠そうとしますが、塩の役目は塩に任せて、鍵を使って秘密を入れた方が良いでしょう。




Answer 3 Joel Hinz


はい、その通りです。関数に関するphpのFAQをなぜ疑うのですか?)

password_hash() の実行結果には、4つの部分があります。

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

?>

独自のソルトを使用したい場合は、以下のように、独自に生成した関数を使用してください。

以下のコードを使用する前に、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/その他の弱いハッシュ)をすべて削除して、ユーザーにアプリケーションのパスワードリセットメカニズムに依存させることを意味しています。