パスワードのハッシュ化とログインの仕様
先日ちょっと出てたパスワードのハッシュ化とログインの仕様について。
とりあえずはログインの仕様についてから書きます。
今まで自分がやっていたログイン方法
if(IDとパスワードの入力欄に何か入っている)
{
SQL(IDとパスワードでselect文を行う);
if(selectの結果、当てはまるものがあったら)
{
ログイン後のページへ遷移;
}
else
{
失敗メッセージと共にもう一度ログイン画面;
}
}
else
{
入力促進メッセージを表示;
}
まだバリデーションを書いてない時の処理はこんな感じでした。
このコードのままだとIDとパスワードを同時にSQLにかけているため、せっかく別々のフィールドなのに実質1つのIDで検索するようなもの。
それに同時にSQLをかけるとIDで間違っているのかパスワードで間違っているかの判断ができなくなるため、バリデーションエラーの数も少なくなって原因が特定しにくくなります。
そこで今回変えた内容がこんな感じ。
if(IDバリデーション処理)
{
SQL1(IDのみで検索);
if(SQL1で何か見つかったら)
{
if(検索したIDにまだロックされていないか)
{
if(パスワードバリデーション処理)
{
SQL2(引っかかったIDとパスワードで検索);
if(SQL2の成否判定)
{
ログイン後のページへ遷移;
}
else
{
if(間違い猶予がまだ指定値未満か)
{
SQL3(間違い猶予のカウントアップ);
パスワード失敗メッセージと残り入力猶予の表示;
}
else
{
SQL4(そのIDのロックフラグをON);
パスワード失敗メッセージとIDにロックがかかったことの通知;
}
}
}
else
{
パスワードの入力促進メッセージ
}
}
else
{
検索したIDはロック中であることを通知;
}
}
else
{
検索したIDは存在しないことを通知;
}
}
else
{
ID入力促進メッセージ;
}
いきなり行数が増えました。
ログインまでの順序として
1.ID入力欄に適正値は入っているか
2.IDの値はデータベースの中に存在するか
3.検索したIDはまだロックされてないか
4.パスワード入力欄に適正値は入っているか
5.パスワードの値は正しいか
この5手順を追ってログインするようにしました。
中でも大事な手順は3のIDがロックされているかどうか。
これも先輩に言われて追加した機能です。
ユーザの名前やパスワードを管理しているデータベースのテーブルに間違い猶予とロックフラグの項目を追加し、実装しました。
こうするだけでも総当たり攻撃は大分防げるらしいです。
他の総当たり攻撃に対する対策としてはログイン入力タイミングが早すぎるとログイン処理を受け付けないというものや、テーブルにダミーパスワードを用意し、そっちに引っかかると何も無いページに飛ばすと言う方法もあるみたいです。
と言ってもあくまで対策であって、時間さえあればどんなパスワードもやぶられることには変わりないです。
前の職場ではある特定の機能を使うのに静脈認証をするということもしてました。
個人ですら良く分からない鍵は確かに強力そうですが個人でも分からない内に使えなくなったりもしそうですね。
指紋認証とかも事故等で指が飛んだりしたら二度と入れなくなりそうですし・・・情報保護っていうのはどうにも難しいものです。
さて、次はパスワードのハッシュ化について。
こっちは不正アクセスなどでパスワードを管理しているテーブルを見られてしまった時の対策です。
ここ数年で個人情報が漏れるというニュースを良く聞きます。yahooも漏れたことがありましたね。
これは個人情報を保存しているテーブルを覗かれ、中に格納された情報が公開される状態になることになります。
yahooの個人情報漏洩ニュースを見返すとユーザIDの流出がおよそ80%に対して、パスワードの流出は5%ほどだったみたいです。
単にユーザIDとパスワードを管理しているサーバや管理パスワードが別々だったのかもしれませんが・・・
で、こんな風に不正アクセスをされた時、パスワードに入力した文字がそのまま入っていると即座にバレてしまうわけです。
即座にバレることを少しでも避けるための悪あがきがハッシュ化になります。
悪あがきって書いたのはハッシュ化も元をたどればパスワード原文なので時間があればいつかはバレるからです。
ハッシュ化は何なのかと言うとアルゴリズムを利用して原文を暗号化する機能のことです。
PHPではシンプルにhash()という関数が用意されてました。
hash()には2つの引数を入れ、第1引数はアルゴリズムの種類、第2引数には原文となる文字列を入れます。
例えばアルゴリズムにmd2を使い、aをハッシュ化すると
<?php echo hash('md2' , 'a')?>
こう書いた部分がこの様に表示されます。
32ec01ec4a6dac72c0ab96fb34c0b5d1
パッと見てこれが元々1文字のアルファベットだった、とは想像が付きにくいですね。
aaを同様にハッシュ化しても
2909579e435315f8ca9b3fb77f373de3
となり、元の文字数が違っても出力結果の文字数は同じになっています。
なので元々の文字が何文字であったかの特定もパッと見ではできないようになっています。
ですが、このハッシュ化した元のアルゴリズムの種類はPHPで用意されてる分では数十種程度。
もっと長い文字列ならまだしも、a一文字だけとかだとアルゴリズムの総当たりで破られてしまいます。
そこで使えるのがハッシュ化の複合使用。
やることは簡単、要するにハッシュ化して出てきた値をさらにハッシュ化すればいいのです。
<?php echo hash( 'md2' ,hash( 'md2' , 'a' ) )?>
e516b5837b49707fe9c8a416ec6fad9d
こうすることで総当たりに時間をかけさせ、HITまでの時間を遅らせることもできます。
が、あくまで遅らせるだけ。何度も言いますが元の値が決まっている以上、解析し続ける機械がある限り、いつかは当たります。
ここからは初心者の考えというか蛇足的なものなので読まなくてもいいです・・・
ハッシュ化後の値をぼーっと見てハッシュ化について気になったこと。
ハッシュ化後の値は全て0~9とa~f(16進数)で構成されてるように思います。
PHPのハッシュ関数のドキュメントのどのアルゴリズムでもg以降のアルファベットは見当たらないので多分合ってます。
で、何を思ったかというと、ハッシュ化することで予期せぬ副パスワードが作られてしまうのではないかと思ったのです。
そう思ったら気になる記事を見つけました。
ハッシュ化した後元に戻せない一方向ハッシュ関数を使っても別の文字列で同じハッシュ化した値が見つかるみたいです。
ハッシュ化も一つのアルゴリズムなので14+42=7*8みたいに元が違っても同じ答えが出る、というのは避けられないことなのかもしれないですね。