16 August 2008

(PHP)ユーザがコントロール可能なセキュア認証

HTTPSやダイジェスト認証が使えない共用レンタルサーバで、よりセキュアな認証を目指す方法。

Perl版はこちら

ユーザ名は
・サーバ側から送付されたランダム・キーを付加してブラウザのJavaScriptでMD5ハッシュ生成
パスワードは
・クライアント側のブラウザのJavaScriptでMD5ハッシュ生成
サーバ側では
・既存のPEAR Auth用のサーバ側の認証DBが流用可能

という手法で、通信系路上でキャプチャされたとしても、ある程度安全が保てるのではなかろうか

ログオン用のメインスクリプト

index.php
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=shift_jis" /> <meta http-equiv="Content-Language" content="ja" /> <title> </title> <script type="text/javascript" src="../cgi-bin/utf.js"></script> <script type="text/javascript" src="../cgi-bin/md5.js"></script> <script type="text/javascript" src="../cgi-bin/authpage_form_md5.js"></script> </head> <body> <?php require_once('auth_md5.php'); require_once('auth_db.php'); $strReturn = CheckAuth('index.php'); if($strReturn) { print("<p>ユーザ ".$strReturn." としてログオンしています</p>"); print("<a href=\"logoff.php\">ログオフする</a>"); } else { print("<p>ログオンしていません</p>"); print("<a href=\"index.php\">ログオン画面を表示する</a>"); } ?> </body> </html>

ログオフ用に、index.phpからリンクが作成されるスクリプト

logoff.php
<html> <body> <?php require_once('auth_md5.php'); LogoffAuth(); ?> <a href="./index.php">再度ログオンする</a> </body> </html>

認証のためのインクルードPHPファイル

auth_md5.php
<?php // 認証メイン関数 // // 認証されている場合 TRUE を返す // 認証されていない場合 FALSE を返す function CheckAuth($strReloadPage) { session_start(); $strAuthUser_authmd5 = ''; // 認証されたユーザ名 if (isset($_SESSION['user'])) { // 既にログオンしてている場合 if($_SESSION['logontime'] + 60 < time()) { // ログオン後、一定時間以上経っている場合は強制ログオフ $_SESSION = array(); // セッション変数を全てクリア session_destroy(); // セッションファイルを削除 return(''); } else { $strAuthUser_authmd5 = $_SESSION['user']; return($strAuthUser_authmd5); } } elseif(!$_POST['user'] || !$_POST['password']) { // クライアントでハッシュを作成する時の、追加文字列(キー)を作成する $strRandSeed = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; $strKey = ''; for($i=0; $i<16; $i++) { $strKey .= $strRandSeed[rand(0,61)]; } // キーをセッション変数に格納しておく $_SESSION['key'] = $strKey; // 新規入力画面を表示する print("<form method=\"post\" action=\"./$strReloadPage\" name=\"form2\">\n"); print("\t<table border=\"0\" cellpadding=\"2\" cellspacing=\"0\" align=\"center\">\n"); print("\t<tr><td colspan=\"2\"><strong>ログオン送信データ</strong></td></tr>"); print("\t<tr><td>ユーザ</td><td><input name=\"user\" size=\"40\" readonly=\"readonly\" /></td></tr>\n"); print("\t<tr><td>パスワード</td><td><input name=\"password\" size=\"40\" readonly=\"readonly\" /></td></tr>\n"); print("\t<tr><td colspan=\"2\"><input type=\"submit\" value=\"ログオンする\" /></td></tr>\n"); print("\t</table>\n"); print("</form>\n"); print("<form action=\"./$strReloadPage\" name=\"form1\">\n"); print("\t<table border=\"0\" cellpadding=\"2\" cellspacing=\"0\" align=\"center\">\n"); print("\t<tr><td colspan=\"2\"><strong>ユーザ入力欄</strong></td></tr>"); print("\t<tr><td>ユーザ</td><td><input type=\"password\" name=\"user\" size=\"40\" /></td></tr>\n"); print("\t<tr><td>パスワード</td><td><input type=\"password\" name=\"password\" size=\"40\" /></td></tr>\n"); print("\t<tr><td>Key</td><td><input name=\"key\" size=\"40\" value=\"$strKey\" readonly=\"readonly\" /></td></tr>\n"); print("\t<tr><td colspan=\"2\"><input type=\"button\" value=\"MD5変換\" onclick=\"convert_to_md5()\" /><input type=\"reset\" value=\"入力欄消去\" /></td></tr>\n"); print("\t</table>\n"); print("</form>\n"); return($strAuthUser_authmd5); } else { // DBを参照して、認証チェックを行う $strAuthUser_authmd5 = QueryMd5User($_POST['user'], $_POST['password'], $_SESSION['key']); if($strAuthUser_authmd5 != '') { // 認証OK $_SESSION['user'] = $strAuthUser_authmd5; $_SESSION['logontime'] = time(); // ログオン時刻を保存 return($strAuthUser_authmd5); } } // 認証失敗 return($strAuthUser_authmd5); } // 認証状態をログオフする function LogoffAuth() { session_start(); $_SESSION = array(); // セッション変数を全てクリア session_destroy(); // セッションファイルを削除 } ?>

認証DBに問い合わせるインクルードPHPスクリプト

auth_md5.php
<?php // 認証に成功したときに、ユーザ名を返す。 // 認証に失敗、DBエラーのときは ''(空の文字列)を返す function QueryMd5User($strUser, $strPassword, $strKey) { $strMySqlSrv = 'localhost:3306'; $strMySqlUser = 'username'; $strMySqlPassword = 'userpassword'; $strMySqlDb = 'auth_db'; // MySQL 接続 if (!($cn = mysql_connect($strMySqlSrv, $strMySqlUser, $strMySqlPassword))) { return ''; } // MySQL DB 選択 if (!(mysql_select_db($strMySqlDb))) { // MySQL 切断 mysql_close($cn); return ''; } // SQL クエリ文(全ユーザを読み出す) $strSql = "select user,password from auth_tbl"; // SQLクエリを実行する if (!($rs = mysql_query($strSql))) { // MySQL 切断 mysql_close($cn); return ''; } // 読み出されたデータ数が1以上であることをチェック if(mysql_num_rows($rs) < 1) { mysql_close($cn); return ''; } while ($item = mysql_fetch_array($rs)) { // 認証テーブル上の、一つ一つのユーザ・パスワードについて、同一か確認する if(md5($strKey.$item['user']) == $strUser && $item['password'] == $strPassword) { // ユーザ名とパスワードが一致した // MySQL 切断 mysql_close($cn); // 認証成功の場合、ユーザ名を返す return $item['user']; } } // MySQL 切断 mysql_close($cn); // 認証テーブル上に一致するユーザが存在しなかった(認証失敗) return ''; } ?>

そして、クライアント側でMD5変換するためのJavaScript関数は、Masanao Izumo氏のページよりコピーして利用させていただいた。

md5.jsutf.jsを用いる 。

authpage_form_md5.js
function convert_to_md5() { var f1 = document.form1; var f2 = document.form2; var data; // 「キー + ユーザ名」のMD5ハッシュ作成 data = utf16to8(f1.key.value + f1.user.value); f2.user.value = MD5_hexhash(data); f1.user.value = ""; // 「パスワード」のMD5ハッシュ作成 data = utf16to8(f1.password.value); f2.password.value = MD5_hexhash(data); f1.password.value = ""; }