Help us understand the problem. What is going on with this article?

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

More than 5 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>
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away