LABO IWASAKI

PHP login hash

ログイン時の色々

ログイン周りを書いていきます。
ややこしくなるのでサニタイズ等の処理は省きます。
排他処理はコンテンツに応じて実装してください。

ここではソルトとハッシュ
それをストレッチングするプログラムを解説します。
実装とは少し異なりますが根本的な部分となります。

ログイン認証

Login Page

まずログイン画面を作ります。
PDOに関してはPDOで色々やるを見てください。
ユーザーがID(メール)とPASSを入力して、その値を受け取る所からです。
まずはザーッと全部書いて、後で分けて説明します。

$dsnsalt1 = $dsn['salt1'];
$dsnsalt2 = $dsn['salt2'];

//---------------------------------------
//ログイン認証
//---------------------------------------
if (isset($_POST["go"])){
    $mail = trim($_POST["mail"]);
    $pass = trim($_POST["pass"]);

//------------SALT作成-----------
$salt1 = pack('H*', $dsnsalt1);
$salt = $mail . $salt1;

//------------STRETCHING--------
$hash = '';
for($i = 0; $i < $dsnsalt2 ; $i++) { $hash = hash('sha256', $hash.$pass.$salt); }

//-------------ERROR--------
    if (empty($_POST["mail"])) { $error = '

IDが空白です

'; } if (empty($_POST["pass"])){ $error = '

パスワードが空白です

';} if (preg_match('|\A[0-9a-z_./?-]+@([0-9a-z-]+\.)+[0-9a-z-]+\z|', $mail)) { $mail = $mail; } else { $error = '

メールアドレスが不正です。

'; } if (preg_match("/\A[a-zA-Z0-9]+\z/", $pass)) { $pass = $pass; } else { $error = '

IDかPASSWORDが違います。

'; } //-------------------------------------- //テーブル情報呼び出し変数代入 //-------------------------------------- if ($error == "" ){ setcookie("logid",$mail,time()+(7 * 24 * 60 * 60)); setcookie("logpass",$pass,time()+(7 * 24 * 60 * 60)); $sql = 'SELECT * FROM login WHERE id= :logid AND pass= :logpass;'; $stmt = $log -> prepare($sql); $stmt -> bindParam(':logid', $mail, PDO::PARAM_STR); $stmt -> bindParam(':logpass', $hash, PDO::PARAM_STR); $stmt -> execute(); if ($table = $stmt -> fetch(PDO::FETCH_ASSOC)) { $_SESSION['time'] = time(); $_SESSION['userno'] = $table['userno']; $_SESSION['umail'] = $table['mail']; header('Location: ログイン後のURL'); exit; } else { $error = '

IDかPASSWORDが違います。

'; } } }

受取りと暗号化

Cahch - Encryption

値の受け取りに関してはtrimはしていますが、排他処理は省いています。
まず1行目と2行目の説明。

$dsnsalt1 = $dsn['salt1'];
$dsnsalt2 = $dsn['salt2'];

ここにはスゴく長い文字列とストレッチングする回数が入っています。
そんなに神経質になる事はないと思うんですが、同じファイルに暗号化のキーと回数を置くのが何か嫌なので、こういう面倒な事をしています。

$dsn = array(
'salt1' => '9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08',
'salt2' => '1000'
);

こんな感じで配列に入れて、別の場所からincludeしています。
具体的には、公開ディレクトリより上位に配置しています。
それを変数に入れてるだけの部分ですね。

多重暗号化

Hash & Stretching

この部分でパスワードグチャグチャにします。
もともと長い文字列をpack関数でさらにややこしくしています。
元の「$dsnsalt1」の段階でココまでやってたらpackは要らない気もします。
でもこれは有名な参考書に書いてあったので昔から真似して取り入れてる。

//------------SALT作成-----------
$salt1 = pack('H*', $dsnsalt1);
$salt = $mail . $salt1;
//------------STRETCHING--------
$hash = '';
for($i = 0; $i < $dsnsalt2 ; $i++) { $hash = hash('sha256', $hash.$pass.$salt); }

3行目:packして出来たソルトとID(メール)を繋げました。
5、6行目:forで指定回数だけストレッチングします。
$hash」にパスワードとソルトとハッシュを繋げて繰り返し(この例では1000回)sha256でハッシュ

照合する

To Collate

エラーがなければデータベースに保管してあるID(メール)とPASSに一致するか確認します。
6、7行目:ユーザーが入力したIDとPASSをクッキーに保存しています。
次回からのログインを簡単にするためです。
数字の部分は期間で「7 * 24 * 60 * 60」は「7日 × 24時間 × 60分 × 60秒」
つまり「1週間はクッキーに保管しておく」という事になります。

//--------------------------------------
//テーブル情報呼び出し変数代入
//--------------------------------------
if ($error == "" ){

   setcookie("logid",$mail,time()+(7 * 24 * 60 * 60));
   setcookie("logpass",$pass,time()+(7 * 24 * 60 * 60));

   $sql = 'SELECT * FROM login WHERE id= :logid AND pass= :logpass;';
   $stmt = $pdo -> prepare($sql);
   $stmt -> bindParam(':logid', $mail, PDO::PARAM_STR);
   $stmt -> bindParam(':logpass', $hash, PDO::PARAM_STR);
   $stmt -> execute();

PDOに関してはPDOで色々やるを見てください。

照合の結果

Collate Result

照合の結果をifで条件分岐します。
1行目:データベースに該当するID(メール)とPASSがあった場合
3行目:今の時間をセッションに入れる(ログイン後ページで使用)
4行目:登録されているユーザー固有の番号をセッションに入れる
5行目:さっきのID(メール)をセッションに入れる。

7行目:headerでログイン後のページにジャンプ
8行目:ここでプログラムから抜ける(ifに一致していた場合)
12行目:該当のユーザーがいなければエラー文を吐き出す

if ($table = $stmt -> fetch(PDO::FETCH_ASSOC)) {

   $_SESSION['time'] = time();
   $_SESSION['userno'] = $table['userno'];
   $_SESSION['usermail'] = $table['mail'];

   header('Location: ログイン後のURL');
   exit;

} else {

   $error = '

IDかPASSWORDが違います。

'; }

userno」とは「1123番」とか、そういう数字です。
これはオートインクリメントで、ユーザー登録時に割り当てられる数字で、変わる事のない固定識別数値です。

次にログイン後のページに入れるプログラムです。
ログイン後に見れるページ全てに入れるので、別ファイルに書いて、全てのページにincludeします。
ログイン後ページのURLを直接見に行ってもログインページに飛ぶように、また、ログイン後に一定時間を過ぎたら自動でログアウトします。

ログイン確認

Confirmation

これも、ひとまずザーッと書きます。

//-----------------------------
//ログイン確認
//-----------------------------
$logid = $_SESSION['usermail'];

if (isset($_SESSION['usermail']) && $_SESSION['time'] + 3600 > time()) {

   $_SESSION['time'] = time();

   $sql = 'SELECT * FROM login WHERE mail= :logid;';
   $stmt = $pdo -> prepare($sql);
   $stmt -> bindParam(':logid', $logid, PDO::PARAM_STR);
   $stmt -> execute();

   $member = $stmt -> fetch(PDO::FETCH_ASSOC);

} else {

   header('Location: ログインページのURL');
   exit;
}

//----------------------------------------
//ログアウト処理
//----------------------------------------
if (isset($_POST["out"])) {

   $_SESSION = array();

   if (ini_get("session.use_cookies")) {

      $params = session_get_cookie_params();
      setcookie(session_id(), '', time() - 4200,
      $params["path"], $params["domain"],
      $params["secure"], $params["httponly"]);
    }

      session_destroy();
      setcookie('id', '', time()-3600);
      setcookie('pass', '', time()-3600);

      header('Location: ログインページのURL');
      exit;
}

チェック部分

Login Check

ちゃんとログインしてて、制限時間以内か確認

//-----------------------------
//ログイン確認
//-----------------------------
$logid = $_SESSION['usermail'];

if (isset($_SESSION['usermail']) && $_SESSION['time'] + 3600 > time()) {

   $_SESSION['time'] = time();
   $sql = 'SELECT * FROM login WHERE mail= :logid;';
   $stmt = $pdo -> prepare($sql);
   $stmt -> bindParam(':logid', $logid, PDO::PARAM_STR);
   $stmt -> execute();

   $member = $stmt -> fetch(PDO::FETCH_ASSOC);

} else {

   header('Location: ログインページのURL');
   exit;
}

4行目:「$logid」にセッション、さっきのID(メール)を代入
6行目:もし、SESSIONにID(メール)が入ってて、ログインした時間が1時間以内なら
(3600は、60分×60秒、という書き方です)
8行目:ifとマッチしていたら「$_SESSION["time"]」を今の時間に更新
9〜12行目:データベースの情報を見にいってます。(必要ならここでデータを引っ張っておくと便利)
17行目:時間切れ、または、ログインしてなかったらログインページへ飛ばす

ログアウト機能

Logout

ログイン後ページはログアウトボタンもあると思います。
ここにはログアウトボタンを押した時の処理を書きます。
ログアウトするとセッションを破棄します。

//----------------------------------------
//ログアウト処理
//----------------------------------------
if (isset($_POST["out"])) {

   $_SESSION = array();

   if (ini_get("session.use_cookies")) {

   $params = session_get_cookie_params();
   setcookie(session_id(), '', time() - 3600,
   $params["path"], $params["domain"],
   $params["secure"], $params["httponly"]);

}

   session_destroy();
   setcookie('id', '', time()-3600);
   setcookie('pass', '', time()-3600);

   header('Location: ログインページのURL');
   exit;
}

ログインの時に、パスワードを暗号化してデータベースに問い合わせました。
もちろん、ユーザー登録時にも、同じ暗号化でID(メール)とPASSをデータベースに保管しています。
ユーザー登録ページには以下のように登録プログラムを書いています。
やってる事はログイン時と同じで、それをデータベースに登録しているだけです。

ユーザー登録

Add User Data

ここでも排他処理は省いています。
登録時にはメールアドレスの重複チェックなどを入れてください。

//-----------
//登録
//-----------
$dsnsalt1 = $dsn['salt1'];
$dsnsalt2 = $dsn['salt2'];

if (isset($_POST["go"])){

   $id = $_POST["mail"];
   $pass = $_POST["pass"];

//------------SALT作成-----------
   $salt1 = pack('H*', $dsnsalt1);
   $salt = $id . $salt1;

//------------STRETCHING--------
   $hash = '';
   for($i = 0; $i < $dsnsalt2; $i++) { $hash = hash('sha256', $hash.$pass.$salt); }

//------------INSERT-------------
   $stmt = $pdo1 -> prepare("INSERT INTO login (dd,id,pass,er,etc) VALUES ('', :id, :pass, :er, :etc)");
   $stmt -> bindParam(':id', $id, PDO::PARAM_STR);
   $stmt -> bindParam(':pass', $hash, PDO::PARAM_STR);
   $stmt -> bindValue(':er', 0, PDO::PARAM_INT);
   $stmt -> bindValue(':etc', 0, PDO::PARAM_INT);
   $stmt -> execute();

   header('Location: iwasaki.php');
   exit;
}

やっている事は同じですね。
submitした結果を任意のDBにSELECTで問い合わせるんじゃなく、INSERTしている所が違うだけです。

関連する記事

Related posts

▶︎ PDOで色々やるまとめ
PDOって何?というちょっとした説明、MySQLに接続する方法(PHPのバージョン別)、INSERT、SELECT、UPDATE、COUNT、SUM、DELETEの方法。また、テーブルを作成する方法などをまとめています。PHPマニュアルで「?」な方は是非読んでください。

▶︎ DISTINCTで重複していない値の数を求める
DBのあるテーブルの値の数をカウントしたい。でも重複した値はカウントしたくない。そんな時に使えるDISTINCTの使い方を書いています。

▶ PHPの配列(array)とimplode、explode
すごい苦手な配列を記事にしました。書いてみると大した事ありませんでした…。

▶ PHPでカレンダーを作る(DEMOあり)
PHPでカレンダーを作ります。コピーしたらそのまま使えると思います。

▶ javascriptでローカルストレージを使う。
localstorage(WEBストレージ)の使い方。ログインフォームへの応用を書いています。