$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

乱数の性質とセッショントークンの作成

ユーザアカウントのログイン機能とか作ってると、何らかの形でセッション用のトークンを作成する機会がある。今まではこれは適当にランダムな値を利用していればいいんでしょと思っていたのだけど、ちょっと違ったのでメモ。

乱数の性質

http://akademeia.info/index.php?%CD%F0%BF%F4によると、乱数には三つの性質がある。

  • 無作為性:統計的な偏りがなく、でたらめな数列になっているという性質。
  • 予測不可能性:過去の数列から次の数を予測できないという性質。
  • 再現不可能性:同じ数列を再現できないという性質。再現するためには、数列そのものを保存しておくしかない。

この時、少なくとも無作為性のみ満たされていると弱い擬似乱数、無作為性と予測不可能性が満たされていると強い擬似乱数、全てが満たされていれば真の乱数と呼ばれる。ソフトウェアだけでは、真の乱数を作ることができず、真の乱数に近いものを作るにはハードウェアの力も借りないといけない。

暗号や推測されないトークンを生成するためには、暗号論的擬似乱数を使う必要がある。暗号論的擬似乱数は無作為性と予測不可能性を満たしたものなので、強い擬似乱数もしくは真の乱数であれば暗号や推測されないトークンのために利用できる。

/dev/random - Wikipediaによると、真の乱数に近いものを作るために/dev/randomが存在する。ただしこちらはエントロピプールが空の場合にはブロックするという性質があるため、ブロックしないものとして/dev/urandomが提供されている。この辺は#22 カジュアルに乱数を使う方法とその注意点 - KAYAC engineers' blogも参考にしたい。/dev/urandomは真の乱数ではないが、暗号用途として使える。つまり、強い擬似乱数として利用できる。


これらから、ユーザ向けにセッショントークンを発行するためには、乱数であれば何でも良いわけではなく、無作為性と予測不可能性を満たしたものを利用しなければならない。予測不可能性を満たしていなければ、過去のセッショントークンの文字列を集めればその後のトークンを予測できるため、セッションハイジャックなどが出来るように見える。

Perlでユーザアカウントのセッショントークンを作成する

実際の実装はどうするかというと、基本的にこういうのは自分で実装するとおかしなことになるので、ライブラリを利用したほうが良い。

よくランダムな文字列を作るモジュールであるString::Randomを使うのはセッショントークンとして適切ではない。https://metacpan.org/pod/String::Randomを見るとわかるが、perlのrandを利用しているのでinsecureですって書いてある。rand - Perldoc Browserを見ても、perlのrandはrand() is not cryptographically secure.と書いてあるので、セッショントークンを作成する目的としては使えない。暗号論的擬似乱数を作成するにはData::Entropy, Crypt::Random, Math::Random::Secure, Math::TrulyRandomあたりを使うのがいいらしい。

さらに使いたいalphabetや長さとかを指定できる便利モジュールとしてPerlではSession::Tokenというものがあるのでこれを使うのが良さそうに見える。内部で/dev/urandomを使いseed値を作成し、それを使ってランダムな文字列を生成してくれる。

こんな感じで、256bitのsession_tokenを作成してくれる。

my $session_token_generator = Session::Token->new(entropy => 256);
my $token = $session_token_generator->get;

ただし、Session::Tokenはforkした後にseedが共有される問題があるので、forkした後も共有される変数にSession::Tokenのインスタンスを格納しているなら、forkした時には再度作りなおす必要があるので注意。

まとめ

今回は乱数の性質について軽くまとめて、ユーザのセッション用のトークンにはどういう性質が必要かについて書いた。またPerlでそのようなものを作るためにはどのようにすれば良いかについても書いた。

セキュリティ周りに関しては、どれだけ調べても自分が正しいことを言っているのかわからないので、何かしら間違ってそうなところがあったらしてきてください。