【PHP】password_hashとpassword_verifyの照合|【筆者が発見した思わぬ落とし穴】

PHP

今回はログイン機能を実装する際の暗号化について解説します。

簡単に自己紹介ですが私は会社員15年程経験し様々な効率化を図ってきましたが評価されず独立。独立後フリーランスエンジニアとしてPHPで福祉業界の非効率な部分をPHPなどを用いて効率化を実現した経験があるので有益な情報になると思います。

今回の記事の対象者
  • ログイン機能を実装する際、どの暗号化技術を使えば良いかわからない
  • password_hashとpassword_verifyを照合しているが一致しない
  • password_hashとpassword_verifyの一致方法が知りたい

上記1つでも当てはまる方は読んで下さい。

今回の記事を見た事で得られる事
  • PHPでログイン機能を実装する際に選ぶべき暗号化技術がわかる
  • password_hashとpassword_verifyを照合しているが一致しない原因がわかる
  • password_hashとpassword_verifyの一致方法がわかる

PHPパスワード暗号化技術3選

  1. md5(非推奨)
  2. sha1(非推奨)
  3. password_hash(推奨)

端的に解説します。

1.md5使用例

<?php
$pass = 'password';
$hashed_pass = md5($pass);
var_dump($hashed_pass);
// 出力結果:string(32) "5f4dcc3b5aa765d61d8327deb882cf99"
?>

2.sha1使用例

<?php
$pass = 'password';
$hashed_pass = sha1($pass);
var_dump($hashed_pass);
// 出力結果:string(40) "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8"
?>

3.password_hash使用例

<?php
$pass = 'password';
$pass_after_hash = password_hash($pass, PASSWORD_DEFAULT);
var_dump($pass_after_hash);
// 出力結果:string(60) "$2y$10$y1ImseJDAz2vFjPT7fvqieYmYMeTaMiZFHGcN0USuZe5Fc0GeMCW6"
?>

【結論】password_hashを使いましょう

理由はmd5とsha1は毎回同じハッシュ値を作ってしまう為。
なのでpassword_hashを使いましょう。

ログイン認証での使い方

テーブル名:login_infoにはlog_pass(カラム名)にpassword_hashで暗号化されたものが入っているという前提です。

データベース名:hogehoge、テーブル名:login_infoが作成されている事を前提に解説します。

log_idとlog_passをname属性でlogin.php(自画面)画面に渡します。

login.phpを作成します。

//データベース名:hogehoge
//テーブル名:login_info
//カラム1:log_name(varchar15)
//カラム2:log_pass(varchar20)※注意

<?php
//前画面のlogin.htmlから値を受け取る
$log_name = $_POST['log_name'];
$log_pass = $_POST['log_pass'];

//データベース接続
$dsn = 'mysql:host=localhost;dbname=hogehoge;charset=utf8';
$user = 'root';
$password = '';//Macの場合は'root'

$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$stmt = $pdo->prepare('SELECT * FROM login_info where log_name = :log_name');
	$stmt->bindValue(':log_name', $log_name, PDO::PARAM_STR);
	$stmt->execute();
	$res = $stmt->fetch(PDO::FETCH_ASSOC);

	if($res && password_verify($log_pass, $res['log_pass'])){
		echo "ログイン成功";
	}else{
		echo "ログイン失敗";
	}

?>

<form action="" method="post">
	<input type="text" name="log_name">
	<input type="password" name="log_pass">
	<input type="submit" value="ログイン">
</form>

上記で

  1. 自画面のname属性を受け取る。(log_nameとlog_pass)
  2. データベース接続する。
  3. 1で受け取ったlog_nameに対応するlog_passをデータベースから参照する。
  4. 参照したlog_nameとlog_passを$resに入れる。
  5. $res[‘log_pass’]には暗号化済の値が入っている。
  6. 3で受け取ったlog_passの平文(暗号化前)とデータベース(hogehoge)のカラム(log_pass)の暗号化済をpassword_verifyで比較して一致していればログイン成功で、一致していなければログイン失敗と出力する。

もしここで何度やってもログイン失敗してしまう方は以下の4つの項目を見直して下さい。

1.password_hashを使ってデータベースに登録する際に、暗号化する部分をダブルクオーテーションで囲っている。
解決策:シングルクオーテーションで囲って下さい。

<?php

// password_hash()を使って"password"をハッシュ化したもの
$hash_after = "$2y$10$y1ImseJDAz2vFjPT7fvqieYmYMeTaMiZFHGcN0USuZe5Fc0GeMCW6";

if(password_verify('password', $hash_after)){
	echo 'ログイン成功';
}else{
	echo 'ログイン失敗';
}

?>

2.password_hashを使ってデータベースに登録する際に、暗号化する部分に空白が入っている。
解決策:空白を取り除いて下さい。

<?php

// password_hash()を使って"password"をハッシュ化したもの
$hash_after = ' $2y$10$y1ImseJDAz2vFjPT7fvqieYmYMeTaMiZFHGcN0USuZe5Fc0GeMCW6 ';

if(password_verify('password', $hash_after)){
	echo 'ログイン成功';
}else{
	echo 'ログイン失敗';
}

?>

3.ハッシュ値同士でデータベースと照らし合わせている。
解決策:ハッシュ値同士で照らし合わさない。

<?php 
$hash_after = 'password';
echo password_hash($hash_after, PASSWORD_DEFAULT);
?>

//1回目に出力されるハッシュ値と2回目では毎回違うハッシュ値が作成されるので
//一致する事はない。
?>

上記のどれにも該当しないのにログイン出来ない場合には
※注意」と記述した部分があるのですが上部を見て下さい。

今から解説する4つ目が私がドハマりした部分です。

4.データベース名:hogehogeのテーブル名:login_infoのテーブル構造を見て下さい。
カラム2:log_pass(varchar20)とあると思いますがこの【varchar20】が原因です。

なぜならpassword_hashは【60文字以上に暗号化する】からです。

つまり、データベースに登録する際に【varchar20】にする事で強引に20文字にして
テーブルに保存されてしまっているのです。
なので【varchar20】を【varchar100】などに変更するとログイン成功するようになりました。

以上、自身の忘備録としても綴っておきます。
皆様にとって有益な情報になったら幸いです。

タイトルとURLをコピーしました