乱数
疑似乱数
新人プログラマ応援

各言語での、本当に安全な乱数の作り方

More than 1 year has passed since last update.


結論

乱数生成と言っても、プログラムの世界では、

ただの疑似乱数生成関数と、暗号論的疑似乱数生成関数の2種類があります。

乱数を作るときは、どちらを使うべきか意識して使いましょう。

特に、


  • 複数回高速実行されうる

  • 並列同時実行されうる

  • 暗号化の用途としてつかう

などの場合は、ただの疑似乱数生成関数ではなく、暗号論的疑似乱数生成関数を使いましょう。


疑似乱数?暗号論的?

(説明とかいいから、さっさと言語別サンプル見せろという方はこちら

頭に暗号論的がつかない、ただの疑似乱数生成関数(PRNG)は、

高速または並列で、同時に実行されると

同じ値を返却してしまうという問題があります。


実験


乱数生成サンプル(C#)

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 平行実行された結果、同時に実行されると、

同じ値を返却してしまうためである。


毎回異なるシードをあたえればいい?

単純に、ランダムな値がほしいなら、それでも大丈夫です。

が、高速でも並行でも他の実行と異なるシードってどう作ればいいのでしょう?

少なくともこれはダメです。


乱数を2回重ねる(C#)

for (int i = 0; i < 10; i++)

{
var r = new Random();
var r2 = new Random(r.Next());
Console.WriteLine(r2.Next());
}

1個目の乱数の結果が同じになるので、

結局シードが同じになり、生成される乱数も同じになります。

幸運にも実行スレッド単位で一意のキーを持っている・取得できるなら、

そのキー+実行回数をシードに与える事で、毎回異なる乱数を取得できますが、

そんな幸運は余りありませんし、そのためにスレッドの一意キーを生成するのは、ナンセンスです。


殺伐としたライブラリに暗号論的疑似乱数生成器(CSPRNG)が!

でも暗号論的疑似乱数生成器が実装されているなら、話は簡単です。


暗号論的疑似乱数生成サンプル(C#)

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)のサンプルソースです。

※データ型は整数だったり、小数だったりバラバラです。:wink:


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

組み込み関数としては用意されていない(?)

アプリケーション側で作って下さい。