August 14, 2008
Webサービスのユーザー情報のテーブルにクレジットカードなどの流出したら困る情報を保存するのはとても怖いことです。
そんな情報は保存しないことが望ましいわけですが、ビジネス的な事情でカード情報を保存せざるを得ないケースも当然あります。
少なくともWEBサーバから直結してるサーバーに、そんな情報は置きたくないところで、最低限カード情報サーバは裏側に専用のapiサーバを置いて、SQL文なんぞで一発で全員の情報にアクセスできないような状態にはしておきたいところですが、昨今情報が流出してしまった老舗ECサイトのように、既に稼働していて簡単には直せないぞ!というシステムで、もし万が一、そういうDB情報が流出してしまったとして、流出したDB情報から、カード番号などの重要情報が特定されるまでの期間を多少なりとも延ばすために、1人日で応急措置ができる暗号化方法について記述してみます。
■やり方
・可逆な暗号化処理で暗号化してDBに保存する。暗号化キーを作成する際には、時間情報などの常に変わる情報とソースコードにしか書かれていない「ひみつの文字」を組み合わせて暗号化を行う。
・動作が確認されたらDBから平文の情報は削除する。
■この方法の適用可能範囲
誰もが知っていて、誰もが狙いたいと思える株式公開されている大企業や有名企業のシステムがターゲットではないことは間違いありません。ターゲットとしては中小企業、ベンチャーのWebシステムということになると思います。(とか書くと怒られそうだな。)
■この方法の限界
・ここで紹介するプログラム上で復号できる以上、暗号化方法が特定されてしまったら暗号化ロジックそのものが持つ暗号強度は無力だと思います。
・結局は、ソースコードに書かれた「ひみつの文字」に基づくキーが特定しにくいことだけに依存します。
・なのでソースコードは流出しないことは大前提となります。
ということで、暗号化手法が特定され、また、総当たりで計算されれば一つの暗号は解読可能で、そこから、どのように最初のキーを作ったのか?が特定されれば、芋づる式に全員のパスワードの復号は可能でしょうが、ソースコードと一緒に流出さえしなければ、それって現実的にどれぐらい時間がかかるのよ?ってのがポイントで、迅速に流出したことさえ検知できれば、実際に不正利用されるトラブルまでの時間稼ぎは可能かもしれません。
(こういうのって実際、普通のパソコンで正しい情報を解読するのにどのぐらい時間かかるんでしょうかね?)
また、暗号化方法を特定して、「ひみつの文字列」を特定しながら、その暗号を解く処理を行うだけのメリットがあるかどうか?も重要なことで、カード情報のような経済的に意味のある情報ならそれを解読する意味があるので頑張るでしょうが、それ以外の情報の場合は、そんなことを解読するメリットもそうない可能性もあるので、そこまでのやる意義は見いだせないかもしれません。
以上の能書きを前提として以下のようなソースになります。
今回は、libmcryptを利用したphpのソースです。
$himitsu_no_angou1= "うわなにをするやめr"; //何か適当な秘密の文字を。
$himitsu_no_angou2= "qあwせdrftgyふじこlp"; //何か適当な秘密の文字を。$raw_passwd = "password"; //これが暗号化したいパスワード
$created_on = date('Y-m-d H:i:s', time()); //データ作成日時。こういうの既に保存されてるでしょ?
//(ホントはmicrotime()の方がよさげ?)
//ここから暗号化開始$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', 'cbc', '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$ks = mcrypt_enc_get_key_size($td);
$key = substr(md5($himitsu_no_angou1 . $created_on . $himitsu_no_angou2 ), 0, $ks);mcrypt_generic_init($td, $key , $iv);
$enc = mcrypt_generic( $td, $row_passwd ) ;
mcrypt_generic_deinit($td);
mcrypt_module_close($td);$iv = bin2hex($iv);
$enc = bin2hex($enc); //暗号化されたデータを扱いやすくしてる//$created_on , $iv , $encの3つの情報をDBに保存しておく。
//元の生情報は捨てる//〜〜ここから復号開始〜〜
$iv = pack("H*", $iv);
$enc = pack("H*", $enc);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', 'cbc', '');
$ks = mcrypt_enc_get_key_size($td);
$key = substr(md5($himitsu_no_angou1 . $created_on . $himitsu_no_angou2 ), 0, $ks);
mcrypt_generic_init($td , $key , $iv);$password = mdecrypt_generic($td, $enc);
//復号完了!DBに保存していた生パスの変わりにこのパスワードを使う。mcrypt_generic_deinit($td);
mcrypt_module_close($td);
この関数を利用するためにはlibmcryptをインストールし、phpを--with-mcrypt= [libdir]をつけてコンパイルしてあげる必要があります。
サーバが数台レベルなら、テストも含めて、なんとか1人日で対処できる範囲だと思いますので、心当たりがありそうなら暫定処置してはいかがでしょうか?
また、もしこの方法より、もっと良い方法があるよという方や、ここで提示している【この方法の限界】以上に問題がありそうだったら、是非教えてください。
なおphpでlibmcryptの組み込み方はぐぐってください。phpを自分達でコンパイルしているところなら簡単にできます。こんな素敵な暗号が簡単に使えるのが素晴らしい。
インストール方法の調査と実際のインストールとソースコードの修正を含めた実作業工数をトータル1人日という見積もりにしています。それ以外に必要な「慎重に事を進めるためにかかる時間」やリリースに必要な時間は、都合にあわせてお見積もりください。