JavaScriptには、ビルトインオブジェクトのMathクラスが持つ静的関数のrandom()
がありますから、通常はそれを利用すれば事足ります。
しかし、よりセキュアな方法があることはあまり知られていません。今回はそれも含めて解説してみます。
Math.random()を使う
最もポピュラーで手軽な方法です。実際のところ、ただランダムな数値が欲しい場合はこれで十分です。
Math.random()
は、0以上で1未満のランダムな浮動小数点を返します。
let randomFloat = Math.random();
console.log(randomFloat); // -> 0.13213566616186645
「下限値および上限値の間のintを取得する」というのがおそらく最も多い使い方です。
const MIN = 5;
const MAX = 10;
let randomFloat = Math.random();
let randomInt = Math.floor(randomFloat * (MAX - MIN)) + MIN;
console.log(randomInt); // -> 6
このコードで、5以上10未満の値を取得できます。
Math.random()の問題点
MDNのMath.random()の記述によると、以下のように警告されています。
Math.random() の提供する乱数は、暗号に使用可能な安全性を備えていません。セキュリティに関連する目的では使用しないで下さい。代わりにWeb Crypto API(より正確にはwindow.crypto.getRandomValues()メソッド)を使用して下さい。
Hackernoonに興味深い記事があります。この記事によると、Math.random()は疑似乱数を生成しているだけで、真の乱数でないことに言及されています。
JavaScript(ECMAScript)は、Math.random()に対するインターフェースを定義してはいますが、その実装はブラウザに一任されています。これはMath.random()に限ったことではありません。
分かりやすく言えば、ECMAScriptは、「基本設計」を担い、ブラウザは「詳細設計」以降のフェーズを担う、という役割分担によってJavaScriptは実現されていると言えます。
話がそれましたが、これは「ブラウザによって、Math.random()の乱数生成強度が全く異なる」ということを意味しています。その事情によって、過去にいくつかの問題を生じさせた経緯もあります。
crypto.getRandomValues()を使う
getRandomValues()
メソッドは、WebCryptoAPIの仕様書で、次のように定義されています。
The getRandomValues method generates cryptographically random values.
暗号的にランダムな値を返す、ということですから、少なくともMath.random()
よりも暗号強度の高い乱数が返ってくることが期待できます。試しに、5つのランダムな値を取得してみましょう。
let arr = new Uint32Array(5);
crypto.getRandomValues(arr);
console.log(arr); // -> Uint32Array(5) [836164506, 2650582686, 3220344852, 2279112908, 4196113461]
Uint32Arrayは、unsigned intの32bitの値の配列です。つまり、この例でのgetRandomValues()
は、[0 ~ 4,294,967,295]までの乱数を生成します。
8bitでも試してみましょう。以下のコードでは0~255までの整数が出力されます。
let arr = new Uint8Array(5);
crypto.getRandomValues(arr);
console.log(arr); // -> Uint8Array(5) [3, 153, 100, 105, 101]
ところで、getRandomValuesは取得する配列のサイズが65536byteを超えると、QuotaExceededErrorをthrowします。実際に試してみます。
let arr = new Uint32Array(16385);
crypto.getRandomValues(arr); // -> Uncaught DOMException: Failed to execute 'getRandomValues' on 'Crypto': The ArrayBufferView's byte length (65540) exceeds the number of bytes of entropy available via this API (65536).
この仕様には少しの注意が必要でしょう。
MINからMAXまでのランダム整数を取得する
さて、上記のような配列では実用的に問題があるかもしれません。少しのロジックを追加して、MIN~MAXまでのランダムな整数を出力するプログラムを組んでみます。
const MIN = 5;
const MAX = 10;
const ARRAY_LENGTH = 5;
let diff = MAX - MIN;
let IntArray = ((maxSize)=>{
if(maxSize < 255) {
return Uint8Array;
}
if(maxSize < 65535 ){
return Uint16Array;
}
if(maxSize < 4294967295){
return Uint32Array;
}
throw new RangeError("MAX must be less than 4294967295");
})(MAX);
let arr = new IntArray(ARRAY_LENGTH);
crypto.getRandomValues(arr);
let randomArray = Array.from(arr, v => (v % diff) + MIN);
console.log(randomArray); // -> [9, 7, 7, 6, 7]
クラスとして実用化するには、Generator関数を使うなどもう少し工夫が必要です。以下の記事も参考にしてみてください。
使いどころ
フロントエンドにおいては、Math.random()
で事足りることが多く、セキュリティ面で使うにしてもSubtleCrypto.generateKey()などがありますから、実際にはあまり使う機会のない関数かもしれません。
あえて使うなら、UUIDの生成器を作成するときくらいでしょうか。
まとめ
今回のように、ランダムな値を1つ得るためにも複数の方法があることがわかります。
プログラムを記述するときは、その場に合ったやり方を考えていきましょう。