Goの乱数生成に関する標準パッケージにはmath/rand
とcrypto/rand
の2つがあります。それぞれmath/rand
は弱い乱数でcrypto/rand
は強い乱数なのですが、強い乱数のほうが必ずしも良いというわけではなく、特性やパフォーマンスの違いでユースケースが分かれています。
math/rand
math/randパッケージは疑似乱数生成器を実装しています。
乱数はソースによって生成されます。
Float64()
やInt()
などのトップレベル関数は、プログラムが実行されるたび、デフォルトの共有ソースを使用し値を生成します。実行する毎に異なる動作が必要なら
Seed()
関数を利用してデフォルトソースを初期化します。デフォルトソースは複数のゴルーチンから並列使用に対して安全です。しかし
NewSource()
から作られたものはそうではありません。セキュリティに適した乱数については
crypto/rand
を参照してください。
通常乱数を使用する場合は math/rand
を利用します。多くの場合time.Now().UnixNano()
を乱数のシード値にすることで、実行するたびに異なる数を得ることができます。
package main
import (
"fmt"
"time"
"math/rand"
)
//0から99のランダムな数値を表示する
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Intn(100))
}
様々な型で乱数を返す関数が用意されており、且つ高速に安定した乱数を得ることができます。
注意点として、トップレベル関数はgoroutineセーフですが、NewSource()
で生成したソースはgoroutineセーフではありません。ただし、トップレベル関数がgotoutineセーフなのは内部でロックを取っているだけなので、乱数を取得する際に同様にロックを取ればgroutineセーフになります。
math/rand
の擬似乱数生成器ではいくつかの数値をもとに、呼び出すたびに組み合わせを変えることで適度に分散した乱数を生成します。( 参考: https://golang.org/src/math/rand/rng.go )元になる数値は一定で、シード値に対応した値を返すため乱数を再現することが可能です。このような無作為な数列になるが、予測が可能な乱数を弱い乱数と呼びます。
ゲームのダイスなどは弱い乱数で十分ですが、暗号化やセッションID生成で利用する場合は脆弱性になり得ます。例えば、セッションIDをmath/rand
の乱数をもとに文字列生成するときに time.Now().UnixNano()
がシード値だった場合、セッションIDを発行した時間帯が判れば総当りでセッションIDを特定できるかもしれません。
math/rand
は十分に分散した数列を高速に得られますが、予測可能であるため暗号論的に必ずしも安全ではありません。
crypto/rand
crypto/randパッケージは暗号学的に安全な乱数生成器を実装しています。
暗号学的に安全な疑似乱数生成器はCSPRNG(cryptographically secure pseudo random number generator)とも呼ばれます。
math/rand
とは異なり、io.Reader
インターフェイスを利用してランダムなバイト列を取り出すことができます。
package main
import (
"crypto/rand"
"fmt"
"math/big"
)
//0から99のランダムな数値を表示する
func main() {
n, err := rand.Int(rand.Reader, big.NewInt(100))
if err != nil {
panic(err)
}
fmt.Println(n)
}
rand.Reader
の中身は環境によって異なります。linuxの場合は/dev/urandom
を利用し、WindowsであればCryptoAPIを利用します。
crypto/rand
で利用されるものはすべてCSPRNGです。linuxではデバイスドライバなどから得られた環境ノイズをシードとして乱数を生成します。そのため、プログラムからシード値を与えることはありません。
ただし、/dev/urandomは一般的に重い処理であるため、math/rand
に比べると環境によってはパフォーマンスが大きく落ちることがあります。
まとめ
抽選や無作為サンプリングなど、再現性があっても十分にランダム性のある数列が得られれば問題ない場合はmath/rand
を利用します。
ランダムかつ暗号的な安全さ(予測困難性)が求められる場合はcrypto/rand
を利用するのが望ましいです。例えば「パスワード・セッションIDの生成」や「暗号化の際に必要なランダムなバイト列の生成」などが挙げられます。