【PHP】登録ページ、ログインページ　ソルトとストレッチを使ってパスを生成

## はじめに
お目汚し失礼いたします。ど素人の勉強ノートです。
使っているレンタルサーバーのPHPバージョンが5.4だったのでpassword_hashが使えず。
徳丸浩さんの『安全なwebアプリケーションの作り方』のサンプルを参照しています。
ご指摘いただけるととても嬉しいです。よろしくお願いします。

・登録内容は、「名前」「パスワード」「登録日」「プロフ画像（リサイズ）」など。
・idがユニークかどうかのチェック
・パスワード確認
自分メモ：もし、画像を登録しなかった時にその他もインサートできていなかったら、mysqlのnullのチェック欄を確認。__チェックを入れて__おくこと。


## 登録ページ
```php:register.php
<?php
session_start();
require_once 'dbmanager.php';//データベース接続
define('FIXEDSALT', '123456789abcdef');
define('STRETCHCOUNT', 1000);
// 初期化
$name = '';
$password = '';
$errors = array();

if(!empty($_POST)) {
    if (!isset($_POST['name']) || $_POST['name'] === '') {
        $errors['name'] = 'ニックネームが入力されていません。';
    } else {
    $name = htmlspecialchars($_POST['name'], ENT_QUOTES);
    $pdo = getDb(); 
    $stmt = $pdo->prepare('select name from members where name = :name');
    $stmt->bindParam(':name', $name, PDO::PARAM_STR);
    $stmt->execute();
    $row = $stmt->fetchAll();
    $count = count($row);
        if ($count > 0) {
            $errors['name2'] = $name.'はすでに使用されているニックネームです。';
        }
    }
    if (!isset($_POST['password']) || $_POST['password'] === '') {
        $errors['password'] = 'パスワードが入力されていません。';
    } else {
        if (strlen($_POST['password']) < 4){
        $errors['passwordlength'] = 'パスワードは４文字以上で入力してください。';
        } else {
        $password = htmlspecialchars($_POST['password'], ENT_QUOTES);
        }
    }
    if (!isset($_POST['password2']) || $_POST['password2'] === '') {
        $errors['password2'] = '確認用パスワードを入力してください。';
    } else {
        $password2 = htmlspecialchars($_POST['password2'], ENT_QUOTES);
        if ($password != $password2){
           $errors['password12'] = 'パスワードが一致していません。';
        } else {

// パスワードをハッシュする
        function get_salt($id){
        return $id . pack('H*', FIXEDSALT);
        }
        function get_password_hash($id, $pwd){
        $salt = get_salt($id);
        $hash = '';
        for ($i = 0; $i < STRETCHCOUNT; $i++){
        $hash = hash('sha256', $hash . $pwd . $salt);
        }
        return $hash;
        }
$password = get_password_hash($name, $password);
        }
    }
}
 
if (isset($_FILES['upfile']['error']) && is_int($_FILES['upfile']['error'])) {
    try {

        // $_FILES['upfile']['error'] の値を確認
        switch ($_FILES['upfile']['error']) {
            case UPLOAD_ERR_OK: // OK
                break;
            case UPLOAD_ERR_NO_FILE:   // ファイル未選択
                throw new RuntimeException('ファイルが選択されていません');
            case UPLOAD_ERR_INI_SIZE:  // php.ini定義の最大サイズ超過
            case UPLOAD_ERR_FORM_SIZE: // フォーム定義の最大サイズ超過
                throw new RuntimeException('ファイルサイズが大きすぎます');
            default:
                throw new RuntimeException('その他のエラーが発生しました');
        }

// $_FILES['upfile']['mime']の値はブラウザ側で偽装可能なので、MIMEタイプを自前でチェックする
        $filename = $_FILES['upfile']['tmp_name'];
        if (!$info = getimagesize($filename)){
             throw new RuntimeException('有効な画像ファイルを指定してください', 400);
        }
        if(!in_array($info[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG], true)){
             throw new RuntimeException('未対応の画像形式です', 400);
        }
        list($width, $height) = $info;
// 縦横比を維持したまま 120 * 120 以下に収まるサイズを求める
            if ($info[0] >= $info[1]) {
                $new_width = 120;
                $new_height = ceil(120 * $info[1] / max($info[0], 1));
            } else {
                $new_width = ceil(120 * $info[0] / max($info[1], 1));
                $new_height = 120;
            }
        $image_p = imagecreatetruecolor($new_width, $new_height);
// 画像処理に使う関数名を決定する
        $create = str_replace('/', 'createfrom', $info['mime']);
        $output = str_replace('/', '', $info['mime']);
        $image = $create($filename);
        imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
ob_start();
$output($image_p);
$img = ob_get_contents();
$_SESSION['img'] = $img;
$_SESSION['type'] = $info['mime'];
ob_end_clean();
        
        $msg = ['green', 'ファイルは正常に読み込まれています。'];

    } catch (RuntimeException $e) {

        $msg = ['red', $e->getMessage()];

    }

}
 
if(isset($_GET['action']) && $_GET['action'] === 'edit'){
 $name = $_SESSION['name'];
}


if(count($errors) === 0){
    $_SESSION['name'] = $name;
    $_SESSION['password'] = $password;
    }
    $date = new DateTime();
    $date = $date->format('Y-m-d H:i:s');

if(!empty($_POST['register'])){
    require_once 'dbmanager.php';
    try{
        $pdo = getDb(); 
        $sql = 'INSERT INTO members(name,password, picture, type, created) VALUES(:name, :password, :picture, :type, :created)';
        $stmt = $pdo->prepare($sql);
        $stmt->bindParam(':name', $_SESSION['name'], PDO::PARAM_STR);
        $stmt->bindParam(':password', $_SESSION['password'], PDO::PARAM_STR);
   $stmt->bindParam(':picture', $_SESSION['img'], PDO::PARAM_STR);
   $stmt->bindParam(':type', $_SESSION['type'], PDO::PARAM_STR);
        $stmt->bindValue(':created', $date, PDO::PARAM_STR);
        $stmt->execute();         
header('Location: thanks.php');
    } catch (PDOException $e) {
        echo $e->getMessage();
    }
}

?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>登録画面</title>
</head>
<body>
<?php
echo "<ul>";
foreach($errors as $value){
echo"<li>";
echo $value;
echo"</li>";
}
echo "</ul>";

?>
<?php if (!isset($_POST['submit']) || $_POST['submit'] === '') : ?>
<form enctype="multipart/form-data" method="post" action="">
<dl>
<dt>ニックネーム<span class="required">必須</span></dt>
<dd><input type="text" name="name" size="35" maxlength="255" value="<?php if(isset($name)){ echo $name; } ?>">
</dd>
<dt>ログインパスワード<span class="required">必須</span></dt>
<dd><input type="password" name="password" size="10" maxlength="20" value=""></dd>
<dt>確認用パスワード<span class="required">必須</span></dt>
<dd><input type="password" name="password2" size="10" maxlength="20" value=""></dd>
<dt>プロフィール画像など(GIF, JPEG, PNGのみ対応)</dt>
<dd><input type="file" name="upfile" /></dd>
<p><input type="submit" value="submit" name="submit"/></p>
</dl>
  </form>
<?php elseif( count($errors) > 0) : ?>
<form enctype="multipart/form-data" method="post" action="">
<dl>
<dt>ニックネーム<span class="required">必須</span></dt>
<dd><input type="text" name="name" size="35" maxlength="255" value="<?php if(isset($name)){ echo $name; } ?>">
</dd>
<dt>ログインパスワード<span class="required">必須</span></dt>
<dd><input type="password" name="password" size="10" maxlength="20" value=""></dd>
<dt>確認用パスワード<span class="required">必須</span></dt>
<dd><input type="password" name="password2" size="10" maxlength="20" value=""></dd>
<dt>プロフィール画像など(GIF, JPEG, PNGのみ対応)</dt>
<dd><input type="file" name="upfile" /></dd>
<p><input type="submit" value="submit" name="submit"/></p>
</dl>
  </form>
<?php else: ?>
<form action="" method="post" enctype="multipart/form-data">
<dl>
<dt>ニックネーム<span class="required">必須</span></dt>
<dd><?php if(isset($name)){ echo $name; } ?></dd>
<dt>ログインパスワード<span class="required">必須</span></dt>
<dd>【表示されません】</dd>
<dt>プロフィール画像など</dt>
<dd>
<?php if (isset($info)): ?>
<img src="UseImg.php" ><br>
<?php endif; ?>
<?php if (isset($msg)): ?>
    <span style="color:<?=$msg[0]?>;"><?=$msg[1]?></span>
<?php endif; ?>
</dd></dl>
<div><a href="?action=edit">&laquo;&nbsp;書き直す</a>|<input type="submit" name="register" value="登録する" /></div>
</form>
<?php endif; ?>
<?php
print $password;
?>
</body>

</html>
```

## ログインページ

```php:login.php
<?php
session_start();
require_once 'dbmanager.php';// データベース接続
$pdo = getDb(); 
// エラーメッセージの初期化
$errorMessage = "";

// ログインボタンが押された場合
if (isset($_POST["login"])) {
  // １．ユーザIDの入力チェック
  if (empty($_POST["name"])) {
    $errorMessage = "ユーザIDが未入力です。";
  } else if (empty($_POST["password"])) {
    $errorMessage = "パスワードが未入力です。";
  } 

  // ２．ユーザIDとパスワードが入力されていたら認証する
  if (!empty($_POST["name"]) && !empty($_POST["password"])) {
    // mysqlへの接続
    $pdo = getDb(); 
    // クエリの実行
    $stmt = $pdo->prepare('SELECT * FROM members WHERE name = :name');
    $stmt->bindParam(':name', $name, PDO::PARAM_STR);
    $stmt->execute();

    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
      // パスワード(暗号化済み）の取り出し
      var_dump($row['password']);
    }

    // データベースの切断
    $db= NULL;

    // ３．画面から入力されたパスワードとデータベースから取得したパスワードのハッシュを比較
    $name = htmlspecialchars($_POST['name'], ENT_QUOTES);
    $pdo = getDb(); 
    $stmt = $pdo->prepare('select password from members where name = :name');
    $stmt->bindParam(':name', $name);
    $stmt->execute();
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    $hash = $row['password'];

        function get_salt($id){
        return $id . pack('H*', FIXEDSALT);
        }
        function get_password_hash($id, $pwd){
        $salt = get_salt($id);
        $hash = '';
        for ($i = 0; $i < STRETCHCOUNT; $i++){
        $hash = hash('sha256', $hash . $pwd . $salt);
        }
        return $hash;
        }
        $password = htmlspecialchars($_POST['password'], ENT_QUOTES);
        $password = get_password_hash($name, $password);

        if ( $password != $hash){
      // 認証失敗
      $errorMessage = "ユーザIDあるいはパスワードに誤りがあります。";

        } else {
      // ４．認証成功なら、セッションIDを新規に発行する
      session_regenerate_id(true);
      $_SESSION["name"] = $_POST["name"];
      if(!empty($_SESSION['return'])){
      $url = $_SESSION['return'];
      header("Location: $url");
      exit;
      } else{
      header("Location: main.php");
      exit;
     }
}
  } else {
    // 未入力なら何もしない
  } 
} 
?>

<!doctype html>
<html>
  <head>
  <meta charset="UTF-8">
  <title>ログインページ</title>
  </head>
  <body>
  <h1>ログイン機能　サンプルアプリケーション</h1>
  <!-- $_SERVER['PHP_SELF']はXSSの危険性があるので、actionは空にしておく -->
  <!--<form id="loginForm" name="loginForm" action="<?php print($_SERVER['PHP_SELF']) ?>" method="POST">-->
  <form id="loginForm" name="loginForm" action="" method="POST">
  <fieldset>
  <legend>ログインフォーム</legend>
  <div><?php echo $errorMessage ?></div>
  <label for="name">ニックネーム</label><input type="text" id="name" name="name" value="
<?php
if(isset($_POST["name"])){
$name = htmlspecialchars($_POST["name"], ENT_QUOTES);
echo $name;
}
?>


">
  <br>
  <label for="password">パスワード</label><input type="password" id="password" name="password" value="">
  <br>
  <input type="submit" id="login" name="login" value="ログイン">
  </fieldset>
  </form>
  </body>
</html>
```

## 参考サイト
・[【初心者向け】PHP5とMySQLでつくるログイン機能のサンプルアプリケーション](http://replication.hatenablog.com/entry/2014/06/30/005815)
・[PHP+MySQLで簡易画像アップローダ](http://qiita.com/mpyw/items/117ab6a88fd58d911c34)
・徳丸浩『安全なWebアプリケーションの作り方』（2014年, SBクリエイティブ）