LoginSignup
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-09-15

はじめに

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

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

登録ページ

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'] = 'パスワードは4文字以上で入力してください。';
        } 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>

ログインページ

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

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

  // 2.ユーザ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;

    // 3.画面から入力されたパスワードとデータベースから取得したパスワードのハッシュを比較
    $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 {
      // 4.認証成功なら、セッション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でつくるログイン機能のサンプルアプリケーション
PHP+MySQLで簡易画像アップローダ
・徳丸浩『安全なWebアプリケーションの作り方』(2014年, SBクリエイティブ)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4