ここでは疑似乱数を取得するためのクラスRandomクラスと暗号乱数ジェネレーターRandomNumberGeneratorクラスについて解説します。

§1 Randomクラス

乱数を得るには、Randomクラスを使用します。 このクラスはMathクラスとは異なり、使用するにはインスタンスを生成する必要があります。

using System;

class Sample {
  static void Main()
  {
    // Randomクラスのインスタンスを作成
    Random rand = new Random();

    // 0以上100未満の乱数を20回取得して表示する
    for (int i = 0; i < 20; i++) {
      Console.Write("{0} ", rand.Next(0, 100));
    }

    Console.WriteLine();
  }
}
実行結果の一例
16 47 34 79 33 54 80 41 99 37 14 86 52 54 52 20 8 31 59 44 

§1.1 乱数の取得

Randomクラスには、乱数を得るためのメソッドがいくつか用意されています。 求める乱数の範囲・種類によって次のメソッドを使い分けることができます。 なお、いずれも得られる乱数の範囲は半開区間(n以上m未満)となっています。

NextDoubleメソッド
0.0以上1.0未満の乱数を実数(Double)で取得する。 [0.0, 1.0)
Nextメソッド
乱数を整数(Int32)で取得する。
Next()
0以上の乱数を取得する。 [0, Int32.MaxValue)
Next(max)
0以上max未満の乱数を取得する。 [0, max)
Next(min, max)
min以上max未満の乱数を取得する。 [min, max)

なお、NextDoubleメソッドには範囲を指定するバージョンは用意されていません。

using System;

class Sample {
  static void Main()
  {
    Random rand = new Random();

    for (int i = 0; i < 20; i++) {
      // 1以上6以下(7未満)の乱数
      Console.Write("{0} ", rand.Next(1, 7));
    }

    Console.WriteLine();

    for (int i = 0; i < 20; i++) {
      // [-1.0, 1.0)の乱数
      Console.Write("{0:F2} ", 2.0 * rand.NextDouble() - 1.0);
    }

    Console.WriteLine();
  }
}
実行結果の一例
1 6 4 6 6 5 3 5 5 2 2 4 3 3 4 6 4 3 1 2 
0.56 -0.21 0.47 0.00 0.65 -0.27 0.21 0.98 -0.66 -0.26 0.01 -0.87 0.97 0.94 0.05 0.82 0.40 -0.27 0.72 -0.85 

NextBytesメソッドを使うと、バイト配列を0からByte.MaxValue(255)までのランダムな値で満たすことができます。 このメソッドを使うことでnonce値の生成などを行うことができます。

using System;

class Sample {
  static void Main()
  {
    byte[] nonce = new byte[16];
    Random rand = new Random();

    // NextBytesを使って配列をランダムな値で満たす
    rand.NextBytes(nonce);

    Console.WriteLine(BitConverter.ToString(nonce));
    Console.WriteLine(Convert.ToBase64String(nonce));
    Console.WriteLine();

    // NextBytesを使わないで同じ処理を行う例
    for (int i = 0; i < nonce.Length; i++) {
      nonce[i] = (byte)rand.Next(0, byte.MaxValue + 1);
    }

    Console.WriteLine(BitConverter.ToString(nonce));
    Console.WriteLine(Convert.ToBase64String(nonce));
    Console.WriteLine();
  }
}
実行結果の一例
5D-DA-A5-7C-17-F5-12-FD-28-12-77-27-8C-41-33-D1
XdqlfBf1Ev0oEncnjEEz0Q==

D1-B4-14-B1-6B-E2-C5-96-58-03-CD-3D-68-43-24-EA
0bQUsWvixZZYA809aEMk6g==

§1.2 乱数のシード

Randomクラスでは、一つのインスタンスが一つの乱数系列として動作します。 個々のインスタンスに異なるシード値(乱数の種)を与えることで複数の乱数系列を生成することができます。 シード値はRandomクラスのコンストラクタで指定することができ、同一のシード値を与えた場合は同一の乱数系列となります。 インスタンスを生成したときのシード値を保存しておくことで、前回と同じ乱数を取得するという使い方もできます。

using System;

class Sample {
  static void Main()
  {
    Random rand1 = new Random(0);
    Random rand2 = new Random(1);
    Random rand3 = new Random(1);

    Console.WriteLine("rand1 rand2 rand3");

    for (int i = 0; i < 15; i++) {
      Console.WriteLine("{0,-5} {1,-5} {2,-5}",
                        rand1.Next(0, 100),
                        rand2.Next(0, 100),
                        rand3.Next(0, 100));
    }
  }
}
実行結果の一例
rand1 rand2 rand3
92    36    36   
25    20    20   
99    95    95   
15    25    25   
12    90    90   
62    36    36   
78    75    75   
69    60    60   
81    83    83   
76    35    35   
88    85    85   
47    54    54   
8     64    64   
63    76    76   
30    67    67   

Randomクラスのコンストラクタにシード値を指定しなかった場合は、システム時計の値(インスタンスを生成するときのEnvironment.TickCountの値)が使用されます。

なお、Environment.TickCountの値は短時間に連続して取得すると同じ値が返される場合があります。 そのため、シード値を指定せず短時間に複数のRandomクラスのインスタンスを生成すると、同一のシード値からなるインスタンス(つまり同一の乱数系列)が生成される可能性がある点に注意が必要です。

using System;
using System.Threading;

class Sample {
  static void Main()
  {
    // シード値を指定せずにインスタンスを生成
    Random rand1 = new Random();
    Random rand2 = new Random();

    Thread.Sleep(250); // 250ミリ秒待機

    Random rand3 = new Random();

    Console.WriteLine("rand1 rand2 rand3");

    for (int i = 0; i < 15; i++) {
      Console.WriteLine("{0,-5} {1,-5} {2,-5}",
                        rand1.Next(0, 100),
                        rand2.Next(0, 100),
                        rand3.Next(0, 100));
    }
  }
}
実行結果の一例
rand1 rand2 rand3
51    51    55   
78    78    55   
88    88    36   
62    62    2    
18    18    36   
6     6     37   
35    35    91   
25    25    13   
66    66    67   
47    47    28   
42    42    1    
23    23    79   
4     4     53   
4     4     14   
48    48    70   

§1.3 乱数アルゴリズムの実装

擬似乱数の生成アルゴリズムにはいくつか種類がありますが、Randomクラスを継承して独自に疑似乱数を実装することもできます。 Randomクラスを継承する際に必要なのは、Sampleメソッドをオーバーライドし、このメソッドで0.0以上1.0未満の乱数を返すように実装することです。

以下の例では、線形合同法を実装したLCGRandomクラスを作成し、Randomクラスが生成する乱数との比較を行っています。 なお、この実装は一つの例として挙げたもので、生成される乱数の周期や分散については考慮していません。 実際に使用する場合は注意してください。

using System;

// 線形合同法による疑似乱数の生成を実装したクラス
class LCGRandom : Random {
  private long x;
  private const long a = 1140671485;
  private const long c = 12820163;

  public LCGRandom()
    : this(Environment.TickCount)
  {
  }

  public LCGRandom(int seed)
  {
    x = seed;
  }

  protected override double Sample()
  {
    x = unchecked(a * x + c) & long.MaxValue;

    return (double)x / long.MaxValue;
  }
}

class Sample {
  static void Main()
  {
    Random rand = new Random(0);
    Random lcg  = new LCGRandom(0);

    Console.WriteLine("Random LCGRandom");

    for (int i = 0; i < 15; i++) {
      Console.WriteLine("{0,-7} {1,-7}",
                        rand.Next(0, 100),
                        lcg.Next(0, 100));
    }
  }
}
実行結果
Random LCGRandom
92      0      
25      0      
99      97     
15      2      
12      65     
62      93     
78      23     
69      65     
81      44     
76      61     
88      37     
47      18     
8       78     
63      82     
30      28     

Sampleメソッドをオーバーライドすることで独自の乱数生成を行えるほか、適切に実装することでメソッドの戻り値の範囲が閉区間となるようにすることもできます。 なお、引数をとらないNextメソッドなど、Sample以外のメソッドもオーバーライドしないと動作が変わらないメソッドがあります。 詳しくはSampleメソッドのリファレンスを参照してください。

この例ではオーバーフロー時に例外のスローを抑止する目的でuncheckedステートメント、および/removeintchecks+コンパイルオプションを使用しています。 詳しくは整数型のオーバーフローとチェックを参照してください。



§2 RandomNumberGeneratorクラス

Randomクラスの疑似乱数は、既知の乱数生成アルゴリズムに基づいています。 そのためRandomクラスが生成する疑似乱数は、次に生成される乱数が予測でき、暗号化などに用いる乱数としては問題となる場合があります。 そこで、このような疑似乱数の代わりとして、予測できない/しにくい乱数を生成するRandomNumberGeneratorクラスを用いることができます。

§2.1 乱数の取得

Randomクラスと同様、乱数を生成するためにはRandomNumberGeneratorクラスのインスタンスを作成する必要があります。 インスタンスを作成するには、Createメソッドを使います。 作成したインスタンスを使って乱数を取得するには、GetBytesメソッドを使います。 GetBytesメソッドの使い方・動作はRandom.NextBytesメソッドと同様です。

using System;
using System.Security.Cryptography;

class Sample {
  static void Main()
  {
    RandomNumberGenerator rng = RandomNumberGenerator.Create();

    byte[] nonce = new byte[16];

    for (int i = 0; i < 3; i++) {
      rng.GetBytes(nonce);

      Console.WriteLine(BitConverter.ToString(nonce));
    }
  }
}
実行結果の一例
44-55-FB-46-91-80-CB-8E-EC-52-5A-06-48-13-46-B5
F7-30-40-46-6E-1B-80-00-B5-61-D5-13-0D-3A-BD-9E
5C-A6-2D-70-FF-55-BA-BF-E5-A1-D1-F8-94-1D-55-A6

Randomクラスとは異なりシード値を指定しなくても、実行する度に毎回異なる乱数を得ることができます。

生成される乱数に0が含まれないようにしたい場合は、GetNonZeroBytesメソッドを使うことができます。

using System;
using System.Security.Cryptography;

class Sample {
  static void Main()
  {
    RandomNumberGenerator rng = RandomNumberGenerator.Create();

    byte[] nonce = new byte[16];

    rng.GetNonZeroBytes(nonce);

    Console.WriteLine(BitConverter.ToString(nonce));
  }
}

Randomクラスとは異なり、NextやNextDoubleなどのメソッドは用意されていません。 そのため、Int32等の乱数が必要な場合は、生成された乱数を含むバイト列から変換する必要があります。

using System;
using System.Security.Cryptography;

class Sample {
  static void Main()
  {
    RandomNumberGenerator rng = RandomNumberGenerator.Create();

    byte[] rand = new byte[4];

    for (int i = 0; i < 20; i++) {
      rng.GetBytes(rand);

      // 1から6までの整数に変換
      int val = 1 + (int)(6.0 * (BitConverter.ToUInt32(rand, 0) / ((double)uint.MaxValue + 1.0)));

      Console.Write("{0} ", val);
    }

    Console.WriteLine();
  }
}
実行結果の一例
4 4 1 5 1 1 3 1 2 2 3 2 4 2 1 3 1 3 5 6

§2.2 RNGCryptoServiceProviderクラス

RandomNumberGeneratorクラスは抽象クラスで、Createメソッドで作成される実際のインスタンスはRNGCryptoServiceProviderクラスです。

Createメソッドの引数で生成する暗号乱数ジェネレータの名前を指定することもできるようになっていますが、デフォルトの状態ではRNGCryptoServiceProvider以外に生成できるジェネレータは用意されていないようです。

また、特にCreateメソッドを使わなくても、直接RNGCryptoServiceProviderクラスのインスタンスを作成することもできます。

using System;
using System.Security.Cryptography;

class Sample {
  static void Main()
  {
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

    byte[] nonce = new byte[16];

    rng.GetBytes(nonce);

    Console.WriteLine(BitConverter.ToString(nonce));
  }
}

.NET Framework 4.0以降では、RandomNumberGeneratorクラスはIDisposableインターフェイスを実装しているので、Dispose()を明示的に呼び出したり、usingステートメントとともに使うことができます。

using System;
using System.Security.Cryptography;

class Sample {
  static void Main()
  {
    using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) {
      byte[] nonce = new byte[16];

      rng.GetBytes(nonce);

      Console.WriteLine(BitConverter.ToString(nonce));
    }
  }
}

なお、.NET Frameworkで実装されているRNGCryptoServiceProviderクラスは、

暗号化サービス プロバイダー (CSP : Cryptographic Service Provider) によって提供された実装を使用して、暗号乱数ジェネレーター (RNG : Random Number Generator) を実装します。

RNGCryptoServiceProvider クラス

とされているため、乱数の取得にCryptGenRandom関数を使っていると思われます。 Monoの実装では、/dev/random, /dev/urandomもしくはWindows上ではCryptGenRandom関数を使用しているようです。

§3 Guid構造体

Guid構造体自体は乱数ジェネレータではありませんが、NewGuidメソッドは一意なGUIDを返すために乱数を使用しているため、このメソッドの戻り値は一種の乱数とみることもできます。 ただ、Guid構造体を乱数として用いる場合は、variantフィールドやバージョン番号などの一部のビットは常に同じ値となる点に注意が必要です。

using System;

class Sample {
  static void Main()
  {
    for (int i = 0; i < 10; i++) {
      Console.WriteLine(Guid.NewGuid());
    }
  }
}
実行結果の一例
5c7932c9-448f-4d75-83b5-67c16ca95ea2
04e56298-75e3-45c8-8403-1ae5fdfb139a
d40b25f4-a912-422a-a071-d3a0bd322050
8d5e9c02-7ccd-4f2d-8565-3123f1acdc1a
4abc0ff5-fcce-4522-815f-e6e89799f138
93ccf3ff-2335-48f3-82c6-32b0ecd3c4a0
dc452781-0c44-4aca-b3ba-72f01a4c60e8
ebf44e59-014c-4acf-a9d4-e4fb60d237ad
2a36e0ae-283c-4a12-b204-310c4fede199
e3288df5-a942-4a77-8bf5-b3c5acc81b1a

この結果にも現れているとおり、xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxxのビットは常に同じ値となります。