🐙

ID/passwordによる認証 > passwordのhash化

2021/08/16に公開

目次ページ https://zenn.dev/gallu/articles/05335420b8e585

前提

前回の https://zenn.dev/articles/d32e47a44ed8a5 はあまりにも洒落にならないので、最低限「パスワードを平文ではない状態で保存する」仕組みです。
現状のPHPだと、パスワードは「password_hash() / password_verify() 関数を使う」のがよいと思われるので、そのように実装をしていきます。

解説

比較的簡単な「IDとパスワードによる認証」で、最低限の処理だけに絞っています。
パスワードの保存の仕方(のうち、推奨されているもの)はいくつかありますが、PHPにおいては「password_hash()を使う」のが手っ取り早いので、PASSWORD_DEFAULT でhash化しておきましょう(本記事を書いているタイミングでは、bcryptが使われていると思われます)。
他にも色々と「追加したい処理」はあるのですが、最低限「パスワードは、適切なアルゴリズムのhashでhash化する(md5でhashとかしない)」という所を踏まえておきましょう。

「ログインIDがそもそも存在しない」場合は、認証NGなので、先頭でとっとと判定をしています。
パスワードの比較は「hash化されたパスワードと生パスワード(入力値)との比較」になるため、password_verify() 関数を使って比較を行うようにします。

想定するテーブルレイアウト

CREATE TABLE `ログインアカウント` (
  `login_id` varbinary(256) NOT NULL COMMENT 'ログインID',
  `password` varbinary(256) NOT NULL COMMENT 'パスワード(password_hash()使用)',
  PRIMARY KEY (`login_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='1レコードが「(1ユーザの)ログイン情報」なテーブル'

実装

入力は一端、 $_POST['login_id']$_POST['password'] で取得可能である、とします。
また、DBハンドルは $dbh にすでに(PDOインスタンスが)入っているもの、とします。
また、「IDが違うのかpasswordが違うのか」がわかるのはあまり好ましくないので、「処理を簡単に関数化」して、戻り値をbooleanにしてエラー処理をします。
関数内の $dbh は「なんらかの方法で取得しているもの」としてください。

実装例

class Authentication
{
    /**
     * 認証処理本体
     *
     * @param string $login_id ログインID
     * @param string $password ログインパスワード
     * @return array|null 認証の可否(arrayなら認証成功、nullなら認証失敗)
     */
    public static function login(string $login_id, string $password) : ?array
    {
        // ごく最低限のvalidate
        if ( ('' === $id)||('' === $password) ) {
            // ログイン失敗
            return null;
        }

        // プリペアドステートメントの作成
        $pre = $dbh->prepare('SELECT * FROM ログインアカウント WHERE login_id=:login_id;');
        // 値のバインド
        $pre->bindValue(':login_id', $login_id);
        // SQLの実行
        $r = $pre->execute();
        // レコードの取得
        $account = $pre->fetch( \PDO::FETCH_ASSOC );

        // レコードが空なら
        if (false === $account) {
            // ログイン失敗
            return null;
        }
        
        // パスワードを比較
        if (false === password_verify($password, $account['password'])) {
            // ログイン失敗
            return null;
        }
        
        // ログイン成功
        return $account;
    }
}

// ログイン
$login_account = Authentication::login(strval($_POST['login_id'] ?? ''), strval($_POST['password'] ?? ''));
if (null === $login_account) {
    // ログイン失敗
    echo 'NG';
    exit;
}
// XXX 以下、認可処理に続く

Discussion