安全な乱数とは何か

PHPでのセキュリティ対策についてのメモについて、第三者に知り得ない文字列の生成(安全な乱数の生成)についてご指摘を受けました(ありがとうございます)。長くなりそうですので、こちらで返信させていただきます。


「第三者が知り得ない文字列(ユーザIDやワンタイムトークンなど)を ハッシュ関数md5関数やsha1関数)を複数回用いて擬似乱数化」< 安全な乱数を理解していない。/ わからないことは「わからない」と書いた方が良い。

これは仰るとおりです。後で引用する水無月ばけらさんも指摘していただいていますが、パスワードが英単語一語などきわめて単純な場合は、ハッシュ関数の組み合わせ(擬似乱数生成アルゴリズム)が相当複雑でない限り、ブルートフォース(総当たり)攻撃で生成語と付き合わせて、生成前の値を突き止められてしまいます(アルゴリズム自体を組み合わせの一つとして考えることもできそう)。

それで、第三者が知り得ない文字列=安全な乱数、を生成するにはどうすればよいか。ばけらさんのご指摘では、安全な疑似乱数を使用して生成する擬似乱数生成器、でしょうか)のが定石だけど、PHP標準では安全な乱数を生成できないのだそうです。

…と、調べた*1限りでは、擬似乱数にも(暗号理論的に)安全なもの[=予想・観測不可能なもの]とそうでないもの[=予想・観測可能なもの]に分けられ、このうち後者の例として、セッションIDの生成に用いられている線形合同法srand関数に用いられているメルセンヌ・ツイスタ法があるようです。

いしなお! - PHPで安全なセッション管理を実現する方法に対する高木さんのコメントへのフォローでは、unix環境で使える /dev/random という乱数デバイスを併用すると良いと書かれています。

この /dev/urandom なるものがどれくらい信用に値するかはもうわからないのですが、Manpage of RANDOM/dev/random - Wikipediaを読む限り、安全でないわけではなさそうです。

ですので、この/dev/urandomのうち、先頭何百バイトかを抜き取り、それを種にしてsrand関数で擬似乱数を生成したものを、mhash関数(SHA-512)を用いてハッシュ値を求めれば大方安全な擬似乱数になるのではないでしょうか。ただ、乱数生成器の取扱い - PHPとPythonを読む限りは、/dev/randomを使わなくとも、だいたい安全な乱数が取れそうです。

<?php $var = bin2hex(mhash(MHASH_SHA512, srand())); ?>

※ もちろん、突き詰めれば予測可能かもしれないですが、そこは「泥棒はもっと不用心な家を狙うんじゃないか理論」を採用しています:)