Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
69
Help us understand the problem. What are the problem?

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

組み込み関数としては用意されていない(?)
アプリケーション側で作って下さい。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
69
Help us understand the problem. What are the problem?