LoginSignup
62
26

More than 3 years have passed since last update.

Goのmath/randとcrypto/rand

Last updated at Posted at 2019-08-05

Goの乱数生成に関する標準パッケージにはmath/randcrypto/randの2つがあります。それぞれmath/randは弱い乱数でcrypto/randは強い乱数なのですが、強い乱数のほうが必ずしも良いというわけではなく、特性やパフォーマンスの違いでユースケースが分かれています。

math/rand

math/randパッケージは疑似乱数生成器を実装しています。

乱数はソースによって生成されます。Float64()Int() などのトップレベル関数は、プログラムが実行されるたび、デフォルトの共有ソースを使用し値を生成します。

実行する毎に異なる動作が必要ならSeed()関数を利用してデフォルトソースを初期化します。

デフォルトソースは複数のゴルーチンから並列使用に対して安全です。しかしNewSource()から作られたものはそうではありません。

セキュリティに適した乱数についてはcrypto/randを参照してください。

通常乱数を使用する場合は math/randを利用します。多くの場合time.Now().UnixNano()を乱数のシード値にすることで、実行するたびに異なる数を得ることができます。

example
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インターフェイスを利用してランダムなバイト列を取り出すことができます。

example
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の生成」や「暗号化の際に必要なランダムなバイト列の生成」などが挙げられます。

62
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
26