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

#参照/引用
PHP逆引きレシピ第2版


#password_hash()関数を使用する
http://php.net/manual/ja/function.password-hash.php

#解説

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

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


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

```php: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()関数で処理する。


```php:lib/h.php
<?php
function h($var)
{
  if (is_array($var)) {
    return array_map('h', $var);
  } else {
    return htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
  }
}
```


#ログイン画面サンプル

```php: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();
}
```

#パスワードの照合

```php:解説
//照合の結果、マッチする場合はtrue、それ以外はfalseが返る
$auth = password_verify($password, $hash);
```



```php: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();
}
```

```php: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>
```