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

ログインフォーム(ソースコード)パスワードのハッシュとID重複チェックなど

More than 3 years have passed since last update.

以前書いたログインフォームはmysql...系とsha-1を使用していたので、訂正なども兼ねて…。
今回はsha256でハッシュするんですが、コレはいつくらいまで大丈夫なんだろう。
こういう暗号とか考えたり破ったりっていう情報の入手先が分からないので定期的に調べないとダメなのかな。

まぁ、ともあれ、ログインフォームって勉強し始めの頃に一番つまずいたところでした。
でも色々面白いところでもあるので色々勉強したなぁ…と思っています。

コッチにも詳しく書いています。【 LABO IWASAKI ログインの暗号化など 】

logo-100-100.png半泣きで覚書 by Takayoshi.Iwasaki

 項目  内容
データベースに接続 【PDOでMySQLに接続】をご覧ください。
SHA-2について 【こんなページ】もありました。
IDとパスについて この記事での取扱いについての説明。
ユーザー登録 IDとパスを登録します。
ログイン認証 データベースのIDパスと一致していたらログイン。
ログイン後のページ ログイン確認と自動ログアウト。
あとがき アドバイスをくれた方への感謝など。

SHA-2

このSHA-2と呼ばれる sha224 とか sha256 とか sha384 sha512 はまだ破られてないみたい。
でもSHA-1 の後継だから同じ手口で破られるのでは?と言われていて、2013年をメドに新しいのを開発するかも知れない。って書いてあった。
shaの後ろの数字はビットの長さを表しています。

IDとパスについて

IDはメールアドレスではなく、任意の文字列を想定して作っています。
パスワードは不可逆暗号化しているので、このままの状態では「パスワード忘れ」の対応が出来ません。
パスワード忘れについては後日UPしようかと考えています。(まだ納得できる状態ではないので)

登録

ここでIDとパスを登録します。
フォームのHTMLは省いています。
● $id に希望のIDを入力してもらい、$pass にパスワードが入っています。
● ハッシュ化は念の為ソルトでやってます。→【理由はコレ】

IDパスワード新規登録(ストレッチングなし)
if (isset($_POST["up"])) {
$id = htmlspecialchars($_POST["id"],ENT_QUOTES);
$pass = htmlspecialchars($_POST["pass"],ENT_QUOTES);
//----------------------
//SHA-2
//----------------------
$password = hash("sha256",$pass);
$passw = "任意の文字列".$password."任意の文字列";
$passd = hash("sha256",$passw);
//----------------------
//空ならエラー
//----------------------
if ($id == "" ) { $error = '<p class="error">IDが入っていません</p>'; }
if ($pass == "" ) { $error = '<p class="error">パスワードが入っていません</p>'; }
//----------------------
//文字数確認
//----------------------
$sid = strlen($id);
$spass = strlen($pass);
if ($sid < 4) {$error ='<p class="error">IDは4文字以上で設定してください</p>';}
if ($spass < 4) { $error ='<p class="error">パスワードは4文字以上で設定してください</p>';
}
//----------------------
// プレグマッチ
//----------------------
if (preg_match("/^[a-zA-Z0-9]+$/", $pass)) { $pass = $pass; }else{
 $error = '<p class="error">パスワードは半角英数で登録してください。</p>'; }
if (preg_match("/^[a-zA-Z0-9]+$/", $id)) { $id = $id; }else{
 $error = '<p class="error">IDは半角英数で登録してください。</p>'; }
//---------------------
//重複チェック
//---------------------
$stmt = $pdo -> query("SELECT * FROM テーブル名");
while($item = $stmt->fetch()) {
if($item['id'] == $id){
  $error = '<p class="error">ご希望のメールアドレスは既に使用されています。</p>';
 }else{
 $id = $id;
 }
}
//-------------------
//DBに登録
//-------------------
if ($error == "" ) {
$stmt = $pdo -> prepare("INSERT INTO テーブル名 (dd,id,pass) VALUES ('', :id, :pass)");
$stmt -> bindParam(':id', $id, PDO::PARAM_STR);
$stmt -> bindParam(':pass', $passd, PDO::PARAM_STR);
$stmt -> execute();
header('Location: login.php');
exit;
 }
}
ストレッチングあり
/** ----------
登録
------------- */
$dsnsalt1 = $dsn['salt1'];//任意の文字が入ります。
$dsnsalt2 = $dsn['salt2'];//ストレッチングの回数
/** 
↑は上位フォルダに置いてるDSN.phpに隠してあるのを呼び出しています
直接値を入れても良いと思います。
*/
if (isset($_POST["go"])){
$id = $_POST["id"];
$pass = $_POST["pass"];
/** ----------
エラーチェック
------------- */
$sid = strlen($id);
$spass = strlen($pass);
if ($sid < 4 ){ $error ='<p>IDは4文字以上で設定してください</p>'; }
if ($spass < 4 ){ $error ='<p>パスワードは4文字以上で設定してください</p>'; }
if(preg_match("/^[a-zA-Z0-9]+$/", $pass))
  { $pass = $pass; }else{
  $error = '<p>パスワードは半角英数で登録してください。</p>'; }
if(preg_match("/^[a-zA-Z0-9]+$/", $id)) { $id = $id; }else{
  $error = '<p>IDは半角英数で登録してください。</p>'; }
/** ----------
 処理
------------- */
if ($error == "") {
//------------SALT作成-----------
$salt1 = pack('H*', $dsnsalt1);
$salt = $id . $salt1;
//------------STRETCHING--------
$hash ='';
for($i = 0; $i < $dsnsalt2; $i++) { $hash = hash('sha256', $hash.$pass.$salt); }
//------------INSERT-------------
$stmt = $pdo -> prepare("INSERT INTO login (dd,id,pass) VALUES ('', :id, :pass)");
$stmt -> bindParam(':id', $id, PDO::PARAM_STR);
$stmt -> bindParam(':pass', $hash, PDO::PARAM_STR);
$stmt -> execute();
header('Location: login.php');
exit;
 }
}

重複チェック

重複チェックのテーブル名はIDとパスワードを保存しているテーブルです。
$pdo はDSN情報です。詳しくは→【PDOでMySQLに接続】
IDとパスワードを保存しているテーブルに同じIDが存在していた場合はメッセージを表示します。

エラーチェック

ここでは「入力されているか」「4文字以上か」「半角英数か」をチェックしています。
ここはもっと他のやり方があると思いますが、今回は最低限にしています。

DBに登録

PDOでデータベースにIDとパスワードを登録します。
INSERTの仕方は【PDOでMySQLにINSERT】で紹介しています。
項目は最小限のIDパスだけにしています。
登録後はlogin.phpに飛ばしています。

star-100-100.png登録が終わりました★ログインします。

ログイン画面

登録済の人が、ログインする画面です。
ここもHTMLは省略しています。
●<input type="text" name=... が [id] と [pass]

ログイン(ストレッチングなし)
if (isset($_POST["in"])){
 $id = htmlspecialchars($_POST['id'],ENT_QUOTES);
 $pass = htmlspecialchars($_POST['pass'],ENT_QUOTES);
//--------------------------------------
//SHA256
//--------------------------------------
 $password = hash("sha256",$pass);
 $passw = "任意の文字列".$password."任意の文字列";
 $passd = hash("sha256",$passw);
//--------------------------------------
//入力確認
//--------------------------------------
 if (empty($_POST["id"])) { $error = '<p class="error">IDを入力してください</p>'; }
 if (empty($_POST["pass"])){ $error = '<p class="error">パスワードを入力してください</p>'; }
//-------------------------------------
//半角英数チェック
//-------------------------------------
 if (preg_match("/^[a-zA-Z0-9]+$/", $pass)) { $pass = $pass; }else{
 $error = '<p class="error">パスワードが不正です。</p>'; }
 if (preg_match("/^[a-zA-Z0-9]+$/", $id)) { $id = $id; }else{
 $error = '<p class="error">IDが不正です。</p>'; }
//--------------------------------------
//IDパスの一致確認
//--------------------------------------
 if ($error==""){
$sql = 'SELECT * FROM テーブル名 WHERE id= :logid AND pass= :logpass;';
$stmt = $pdo -> prepare($sql);
$stmt -> bindParam(':logid', $idd, PDO::PARAM_STR);
$stmt -> bindParam(':logpass', $passd, PDO::PARAM_STR);
$stmt -> execute();
if ($table = $stmt -> fetch(PDO::FETCH_ASSOC)) {
 $_SESSION['id'] = $table['id'];
 $_SESSION['time'] = time();
 header('Location: mypage.php');
 exit;
 }else{
 $error = '<p class="error">IDとパスワードを確認してください</p>';
   }
  }
 }
ストレッチングあり
//----------------------
//LOG IN
//----------------------
$dsnsalt1 = $dsn['salt1'];
$dsnsalt2 = $dsn['salt2'];

if (isset($_POST["go"])){
$id = htmlspecialchars($_POST['id'],ENT_QUOTES);
$pass = htmlspecialchars($_POST['pass'],ENT_QUOTES);
//------------SALT作成-----------
$salt1 = pack('H*', $dsnsalt1);
$salt = $id . $salt1;
//------------STRETCHING--------
$hash = '';
for($i = 0; $i < $dsnsalt2 ; $i++) { $hash = hash('sha256', $hash.$pass.$salt); }
//---------------------
if (empty($_POST["name1"])) { $error = '<div class="error"><p>IDが空白です</p></div>'; }
if (empty($_POST["pass1"])){ $error = '<div class="error"><p>パスワードが空白です</p></div>';}
if (preg_match("/\A[a-zA-Z0-9]+\z/", $id)) { $id = $id; }
else { $error = '<p class="error">IDかPASSWORDが違います</p>'; }
if (preg_match("/\A[a-zA-Z0-9]+\z/", $pass)) { $pass = $pass; }
else { $error = '<p class="error">IDかPASSWORDが違います</p>'; }
//--------------------------------------
//テーブル情報呼び出し変数代入
//--------------------------------------
if ($error==""){
setcookie("logida",$id,time()+(24 * 60 * 60));
setcookie("logpassa",$pass,time()+(24 * 60 * 60));
$sql = 'SELECT * FROM login WHERE id= :logid AND pass= :logpass;';
$stmt = $pdo1 -> prepare($sql);
$stmt -> bindParam(':logid', $id, PDO::PARAM_STR);
$stmt -> bindParam(':logpass', $hash, PDO::PARAM_STR);
$stmt -> execute();
if ($table = $stmt -> fetch()) {
$_SESSION['id'] = $table['id'];
$_SESSION['time'] = time();
header('Location: mypage.php');
exit;
}else{ $error = '<p class="error">IDかPASSWORDが違います</p>'; }}}

ログイン認証

データベースに登録したIDとパスワードと一致していたらmypage.phpに飛ばしています。
その際、SESSIONにIDと時間を記憶しています。(mypage.phpで使います)
SESSIONに関してはもう少し勉強してから書こうかと思います。

heart-100-100.pngログイン後のページにも確認を。

ログイン後に見れるページ

ログイン後にマイページに飛ぶとして、mypage.phpというページとします。
ログインしていない状態でmypage.phpにアクセスしてもlogin.phpへリダイレクトします。
また、ログインしてから1時間以上アクションがなかった場合は自動でログアウトします。

ログイン後のページ
//-----------------------------
//ログイン確認
//-----------------------------
$logid = $_SESSION['id'];
if (isset($_SESSION['id']) && $_SESSION['time'] + 3600 > time()) {
$_SESSION['time'] = time();
$sql = 'SELECT * FROM テーブル名 WHERE id= :logid;';
$stmt = $pdo -> prepare($sql);
$stmt -> bindParam(':logid', $logid, PDO::PARAM_STR);
$stmt -> execute();
$member = $stmt -> fetch(PDO::FETCH_ASSOC);
}else{
header('Location: login.php');
exit;
}
//----------------------------------------
//ログアウト処理
//----------------------------------------
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: index.html');
exit;
}

($_POST["out"])はログアウトボタンを押した時の処理です。
SESSIONハイジャックなどの対策に関してはもっと勉強してから書こうと思います。

cara-100-100.png最後に…。

読んで頂いてありがとうございます。
何故、コレを書こうと思ったのかの説明をしておきたかったのであとがきでも…と。

そもそも、こういう記事自体を書こうと思ったのは、僕が初めてPHPとかを勉強しようと思った時に「もっと色んな記事があればイイのに」と思った事が根本です。
全部独学だから少しでも情報が欲しかったんです。
まだ勉強中なので今でももっと欲しいと思っています。(他の人はどうやって学んでるんだろう…)

…で、ログインフォームに関しては以前にも書いたんですが、結構レガシーな感じだったので、書き直ししようと。

「それ変だよ」とか「それ、どうなのよ」というコメントや親切な方からのアドバイスを頂いた事もあり、今回に至ります。(本当にありがとうございます)
書いたら、また誰かが声を掛けてくれるかも知れないし。という期待もあります。

間違ってたら恥ずかしいけど、誰にもチェックしてもらえないと何も知らないままなので、それなら書こう。という感じです。
ですので、「おかしい」所があれば是非アドバイスを頂けたらと思っています。
どうか宜しくお願いします。

tabo_purify
WEBに特化しています。 その他の言語は分かりません。 間違いを発見した方はご指摘くださいませ。
https://labo-iwasaki.com
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした