LoginSignup
5
7

【Go】math/randとcrypto/randの使い分けについて

Last updated at Posted at 2024-03-28

はじめに

こんにちは、kenです。お仕事ではGoを書いています。
最近「もう一段Goと仲良くなりたいなー」と思い、Go Code Review Commentsというサイトを読んでいたところ、math/randcrypto/randの関係について初めて知ったことがあったので、今回はそれについて書こうと思います。
今まではどちらも乱数を生成するライブラリという認識でしたが、両者には明確な違いが存在していました。

math/randとcrypto/rand

私が勉強になったGo Code Review CommentsのCrypto Randの節を引用します。

Do not use package math/rand to generate keys, even throwaway ones. Unseeded, the generator is completely predictable. Seeded with time.Nanoseconds(), there are just a few bits of entropy. Instead, use crypto/rand’s Reader, and if you need text, print to hexadecimal or base64:
(筆者訳:math/randパッケージを使用してキーを生成することは避けてください、たとえ一時的に使用するものであってもです。シードされていない乱数生成器は完全に予測可能です。time.Nanoseconds()でシードした場合でも、利用可能なエントロピー(ランダム性の度合い)は非常に限られています。代わりに、crypto/randReaderを使用してください。テキスト形式のキーが必要な場合は、生成されたデータを16進数またはbase64形式で出力してください。)

なるほど。math/randは擬似乱数なので本当の乱数を得たいならcrypto/randを使用するほうがよいということですね。この記述を実際にコードを動かして確かめてみます。

package main

import (
	cryptoRand "crypto/rand"
	"fmt"
	mathRand "math/rand"
)

func main() {
	mathRand.Seed(123)
	fmt.Println(mathRand.Intn(100))
	fmt.Println(mathRand.Intn(100))

	buf := make([]byte, 16)
	_, err := cryptoRand.Read(buf)
	if err != nil {
		panic(err)
	}
	fmt.Println(buf)
}

上記を実行すると以下が出力されました。math/randパッケージを使って生成する35と49は何度やっても結果が変わりませんが、crypto/randパッケージを使用して生成したbyteスライスは実行するたびに結果が変わります。

35
49
[218 134 201 39 126 101 220 85 108 53 23 27 101 194 101 196]

実際に下のリンクから試していただけます。

いざ手元で試してみると納得できますね。
そして「time.Nanoseconds()でシードした場合でも、利用可能なエントロピー(ランダム性の度合い)は非常に限られています」というのはおそらく乱数生成のシードに生成時間を用いたとしても安全性は担保できないということだと思います。
つまり攻撃者がこの乱数の生成日時を知っている場合、シード値がその瞬間のナノ秒に基づいていると推測する可能性があり、乱数列を突き止めるおそれがあるということです。

crypto/randとmath/randの使い分け

さて、両者の違いはわかりましたが、これらはどのように使い分ければよいのでしょうか。

crypto/randを使ったほうが良い場合

こちらはいわずもがなですが、暗号鍵やセキュリティトークンなど、予測不可能な乱数を生成したいときに使うべきです。逆にこのような場合ではmath/randを使うべきではありません。

math/randを使ったほうが良い場合

疑似乱数って乱数に勝てる要素あるの?パフォーマンスが良いくらい?と思ってましたが、テストを実行する際に便利な場合があるみたいです。

例えば、テスト対象の関数にランダムな入力を送りつけたい場面があったとします。その際疑似乱数を使っておけば、もしエラーが発生した場合にそのテストケースで使用したシード値を記録しておくことでテストケースを何度でも再現できます。「シード値が固定なら乱数も固定されてしまう」というデメリットがうまくメリットに置き換わっていますね。逆転の発想すごい...!

最後に

両者の特性を理解して、状況に応じて適した方を使うように心がけたいですね。
これからもGo Code Review Commentsで初めて知ったことがあれば記事にしていこうと思います。
最後まで読んでいただきありがとうございました。間違いなどあればコメントにてご指摘ください。

5
7
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
5
7