[Node.js] Math.random()よりセキュアな乱数を生成する

JavaScriptで乱数を利用したい場合はMath.random()を使うことがほとんどだと思いますが、内部のロジック的に暗号などセキュアな用途には向いていないとされています。Node.jsでは標準モジュールであるcryptoを利用することでこの問題を解決することができます。

サンプルコード

結論から言うと具体的なコードは以下になります。getSecureRandom()関数を実行する度にランダムな整数を取得できます。

const Crypto = require("crypto");

function getSecureRandom(){
  const buff = Crypto.randomBytes(8);  // バイナリで8byteのランダムな値を生成
  const hex  = buff.toString("hex");   // 16進数の文字列に変換

  return ( parseInt(hex,16) );         // integerに変換して返却
}

パフォーマンス

気になるのは実行時の速度ですね。もちろんcryptoモジュールのほうが重たいのは想像できるのですがどの程度の差が生まれるのか計測してみましょう。今回は以下の2種類のコードを用意しました。

Math.random()版

Math.random()を100万回実行するシンプルなコードです。

for(let i=0; i<1000000; i++){
  Math.random();
}

crypto版

こちらも同様に100万回実行します。

const Crypto = require("crypto");
for(let i=0; i<1000000; i++){
  const buff = Crypto.randomBytes(8);
  const hex  = buff.toString("hex");
  parseInt(hex,16);
}

実行結果

前者がMath.random()、後者がcryptoモジュールです。

$ time node test1.js
node test1.js  0.09s user 0.03s system 91% cpu 0.132 total

$ time node test2.js
node test2.js  4.04s user 0.17s system 105% cpu 3.997 total

最終的な実行スピードだけ見るとなんと約30倍の差があることがわかりました(3.997/0.132)。ちなみにCrypto.randomBytes()で生成するバイト数を2〜4に減らしても結果はそれほど大きく変わりませんでした。

ゲームやアニメーションのようなパフォーマンスを求められる場面では普通にMath.random()を使ったほうが良さそうですね。まぁそもそも使わないと思いますがw

なお今回実行したNodeのバージョンは以下になります。

$ node --version
v12.6.0

Webブラウザの場合

GoogleChromeなどWebブラウザで実行する場合はwindow.crypto.getRandomValues()で同様にセキュアな乱数を生成可能です。具体的なコードはMDNを参照ください。 developer.mozilla.org

執筆時点(2019-07-14)だと「Can I use」によると96.3%のブラウザが利用可能なようです。

参考ページ

ハンズオンNode.js

ハンズオンNode.js

Amazon