結論
乱数生成と言っても、プログラムの世界では、
ただの疑似乱数生成関数と、暗号論的疑似乱数生成関数の2種類があります。
乱数を作るときは、どちらを使うべきか意識して使いましょう。
特に、
- 複数回高速実行されうる
- 並列同時実行されうる
- 暗号化の用途としてつかう
などの場合は、ただの疑似乱数生成関数ではなく、暗号論的疑似乱数生成関数を使いましょう。
疑似乱数?暗号論的?
(説明とかいいから、さっさと言語別サンプル見せろという方はこちら)
頭に暗号論的がつかない、ただの疑似乱数生成関数(PRNG)は、
高速または並列で、同時に実行されると
同じ値を返却してしまうという問題があります。
実験
for (int i = 0; i < 10; i++)
{
var r = new Random();
Console.WriteLine(r.Next());
}
1319665291
1319665291
1319665291
1319665291
1319665291
1319665291
1319665291
1319665291
1319665291
1319665291
高速実行、または同時実行で同じ値が返却される理由
これが起きてしまうのは、ただの疑似乱数は
- シードが同じなら、同じ結果を返す
- シードを指定しないと、通常、現在時刻(ミリ秒単位)を使う(言語によっては微妙に異なるかも)
ためで、
つまり、高速 or 平行実行された結果、同時に実行されると、
同じ値を返却してしまうためである。
毎回異なるシードをあたえればいい?
単純に、ランダムな値がほしいなら、それでも大丈夫です。
が、高速でも並行でも他の実行と異なるシードってどう作ればいいのでしょう?
少なくともこれはダメです。
for (int i = 0; i < 10; i++)
{
var r = new Random();
var r2 = new Random(r.Next());
Console.WriteLine(r2.Next());
}
1個目の乱数の結果が同じになるので、
結局シードが同じになり、生成される乱数も同じになります。
幸運にも実行スレッド単位で一意のキーを持っている・取得できるなら、
そのキー+実行回数をシードに与える事で、毎回異なる乱数を取得できますが、
そんな幸運は余りありませんし、そのためにスレッドの一意キーを生成するのは、ナンセンスです。
殺伐としたライブラリに暗号論的疑似乱数生成器(CSPRNG)が!
でも暗号論的疑似乱数生成器が実装されているなら、話は簡単です。
for (int i = 0; i < 10; i++)
{
var randomByte = new byte[4];
using(var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
{
rng.GetBytes(randomByte);
}
Console.WriteLine(System.BitConverter.ToInt32(randomByte,0));
}
-1078661811
510582217
-94865278
-602342842
-1315655704
-1868274994
-2112133054
-266973531
248470422
-1808682364
この通り、同時実行されても、それぞれ全く異なる乱数が手に入ります。
各言語のサンプルコード
以下、各言語でのただの~(PRNG)と暗号論的~(CSPRNG)のサンプルソースです。
※データ型は整数だったり、小数だったりバラバラです。
C#
PRNG
var rng = new Random();
Console.WriteLine(rng.Next());
CSPRNG
var randomByte = new byte[4];
using(var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
{
rng.GetBytes(randomByte);
}
Console.WriteLine(System.BitConverter.ToInt32(randomByte,0));
vb.net
PRNG
Dim rng As New Random()
Console.WriteLine(rng.Next())
CSPRNG
Dim randomByte As Byte() = New Byte(3) {}
Using rng As New System.Security.Cryptography.RNGCryptoServiceProvider()
rng.GetBytes(randomByte)
End Using
Console.WriteLine(System.BitConverter.ToInt32(randomByte, 0))
PHP
PRNG
echo rand()
CSPRNG
echo random_int(PHP_INT_MIN,PHP_INT_MAX)
Java
PRNG
double randomNumber = Math.random();
System.out.println(randomNumber);
CSPRNG
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public static void main (String args[]) {
try {
SecureRandom randomNumber = SecureRandom.getInstance("SHA1PRNG");
System.out.println(randomNumber.nextDouble());
} catch (NoSuchAlgorithmException nsae) {
// 例外ハンドラ
}
}
Python
PRNG
import random
print(random.random())
CSPRNG
import os
import sys
import random
rng = random.SystemRandom()
print(rng.randint(0, sys.maxsize))
node.js
PRNG
console.log(Math.random())
CSPRNG
標準では用意されていないが、random-number-csprngを使えば大丈夫
以下は、掲載されていたサンプルソース
var Promise = require("bluebird");
var randomNumber = require("random-number-csprng");
Promise.try(function() {
return randomNumber(10, 30);
}).then(function(number) {
console.log("Your random number:", number);
}).catch({code: "RandomGenerationError"}, function(err) {
console.log("Something went wrong!");
});
javascript
PRNG
console.log(Math.random());
CSPRNG
var array = new Uint32Array(1);
window.crypto.getRandomValues(array);
console.log(array[0]);
※なお、ブラウザによっては、window.crypto.getRandomValues()
が使えない場合があります。
※その場合、適切な代替手段はありません。
SQL Server
PRNG
SELECT RAND()
CSPRNG
SELECT CAST(CRYPT_GEN_RANDOM(8000) AS INT)
MySql
PRNG
SELECT RAND()
CSPRNG
組み込み関数としては用意されていない。
アプリケーション側で作って下さい。
mongoDB
PRNG
print(Math.random())
CSPRNG
組み込み関数としては用意されていない(?)
アプリケーション側で作って下さい。