Edited at

パスワードのハッシュ化とログイン画面

More than 3 years have passed since last update.


参照/引用

PHP逆引きレシピ第2版


password_hash()関数を使用する

http://php.net/manual/ja/function.password-hash.php


解説

ハッシュ関数としてよく使われるmd5()関数やsha1()関数をそのまま使用することは推奨されない。総当たり攻撃に弱いため。

パスワードのハッシュ化で重要なことはその計算量とソルト。パスワード文字列にソルトと呼ばれるデータを追加し、実質的なパスワード文字列を長くすることで安全性を高めることができる。


password_hash()関数を使用してハッシュ化済のパスワードを得るサンプル


hash.php


<?php
//PHP5.5より前(PHP5.3.7以降)を使用する場合はpassword_compactライブラリを使う
// require_once 'lib/password.php';
require_once 'lib/h.php';

if (isset($_POST['submit'])) {
$password = $_POST['password'];

# ハッシュ処理の計算コストを指定します
$options = array('cost' => 10);
# ハッシュ化方式にPASSWORD_DEFAULTを指定し、パスワードをハッシュ化する。
# password_hash()関数は自動的に安全なソルトを生成してくれる。(ハッシュ値を取得するたびにソルトが自動生成されるので、同じパスワードでもハッシュ値が変わる)
$hash = password_hash($password, PASSWORD_DEFAULT, $options);
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>ハッシュ化済みパスワードを取得するスクリプト</title>
</head>
<body>
<div>
<?php
if (isset($hash)) {
echo '生パスワード: ' . h($password) . '<br>';
echo 'ハッシュ化済みパスワード: ' . h($hash);
}
?>
<hr>
<form action="" method="post">
<label for="password">ハッシュ化したいパスワード文字列:</label>
<input type="text" name="password" id="password" value="">
<input type="submit" name="submit" value="ハッシュ化">
</form>
</div>
</body>
</html>



PHP5.3.7以降

password_compactライブラリ(MITライセンス)

https://github.com/ircmaxell/password_compat


エスケープ

http://php.net/manual/ja/function.htmlspecialchars.php

下記は、引数が文字列と配列のどちらも処理できる。

is_array()関数で引数が配列かどうかを判定。配列だった場合は、array_map()関数で全ての要素をh()関数で処理する。


lib/h.php

<?php

function h($var)
{
if (is_array($var)) {
return array_map('h', $var);
} else {
return htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
}
}


ログイン画面サンプル


index.php


<?php
//require_once 'lib/password.php'; php5.5より前の場合使う
require_once 'lib/h.php';

# クリックジャッキング対策
header('X-FRAME-OPTIONS: SAMEORIGIN');

# セッションを開始します。
session_start();

//複数人設定することができる
$userid[] = ''; // ユーザーID
$username[] = ''; // お名前
// パスワードをハッシュ化した文字列
$hash[] = '';

# エラーメッセージ初期化
$error = '';

# 認証済みかどうかのセッション変数を初期化
if (! isset($_SESSION['auth'])) {
$_SESSION['auth'] = false;
}

if (isset($_POST['userid']) && isset($_POST['password'])) {
foreach ($userid as $key => $value) {
if ($_POST['userid'] === $userid[$key] &&
# 入力されたパスワード文字列とハッシュ化済みパスワードを照合
password_verify($_POST['password'], $hash[$key])) {
# セッション固定化攻撃対策(セッションIDを変更)
session_regenerate_id(true);
$_SESSION['auth'] = true;
$_SESSION['username'] = $username[$key];
break;
}
}
if ($_SESSION['auth'] === false) {
$error = 'ユーザーIDかパスワードに誤りがあります。';
}
}

if ($_SESSION['auth'] !== true) {
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>ログイン</title>
</head>
<body>
<div id="login">
<h2>ログイン</h2>
<?php
if ($error) {
echo '<p style="color:red;">' . h($error) . '</p>';
}
?>
<form action="<?php echo h($_SERVER['SCRIPT_NAME']); ?>" method="post">
<p>ユーザーID:<input type="text" name="userid" id="userid" value=""></p>
<p>パスワード:<input type="password" name="password" id="password" value=""></p>
<p><input type="submit" name="submit" value="ログイン"></p>
</form>
</body>
</html>
<?php
# スクリプトを終了し、認証が必要なページが表示されないように
exit();
}



パスワードの照合


解説

//照合の結果、マッチする場合はtrue、それ以外はfalseが返る

$auth = password_verify($password, $hash);


login.php

<?php

//require_once 'lib/password.php'; php5.5より前の場合使う
require_once 'lib/h.php';

# クリックジャッキング対策
header('X-FRAME-OPTIONS: SAMEORIGIN');

# セッションを開始します。
session_start();

//複数人設定することができる
$userid[] = ''; // ユーザーID
$username[] = ''; // お名前
// パスワードをハッシュ化した文字列
$hash[] = '';

# エラーメッセージ初期化
$error = '';

# 認証済みかどうかのセッション変数を初期化
if (! isset($_SESSION['auth'])) {
$_SESSION['auth'] = false;
}

if (isset($_POST['userid']) && isset($_POST['password'])) {
foreach ($userid as $key => $value) {
if ($_POST['userid'] === $userid[$key] &&
# 入力されたパスワード文字列とハッシュ化済みパスワードを照合
password_verify($_POST['password'], $hash[$key])) {
# セッション固定化攻撃対策(セッションIDを変更)
session_regenerate_id(true);
$_SESSION['auth'] = true;
$_SESSION['username'] = $username[$key];
break;
}
}
if ($_SESSION['auth'] === false) {
$error = 'ユーザーIDかパスワードに誤りがあります。';
}
}

if ($_SESSION['auth'] !== true) {
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>ログイン</title>
</head>
<body>
<div id="login">
<h2>ログイン</h2>
<?php
if ($error) {
echo '<p style="color:red;">' . h($error) . '</p>';
}
?>
<form action="<?php echo h($_SERVER['SCRIPT_NAME']); ?>" method="post">
<p>ユーザーID:<input type="text" name="userid" id="userid" value=""></p>
<p>パスワード:<input type="password" name="password" id="password" value=""></p>
<p><input type="submit" name="submit" value="ログイン"></p>
</form>
</body>
</html>
<?php
# スクリプトを終了し、認証が必要なページが表示されないように
exit();
}



logout.php

<?php

# セッション開始
session_start();
# セッションを破棄
$_SESSION = array();
session_destroy();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>ログアウト</title>
</head>
<body>
<div>
<p>ログアウトしました。</p>
<p><a href="">戻る</a></p> <!-- URLを入力 -->
</div>
</body>
</html>