目录

Golang 生成随机数 - 真随机数与伪随机数

在GoLang 中,我们可以通过 math/rand 包里的方法来生成一个伪随机数:

package main

import (
  "fmt"
  "math/rand"
)

func main() {
  fmt.Println(rand.Int())   // => 134020434
}

但如果你在本地,运行和我一样的代码,无论运行多少次,输出都是一样的

随机种子

Golang的math/rand库会使用默认的随机种子——1来生成随机数。

我们知道,伪随机数,是使用一个确定性的算法计算出来的似乎是随机的数序,因此伪随机数实际上并不随机。如果在一开始输入这个算法的创世参数固定的话,算法生成的就是固定结果。

使用时间戳所谓随机种子

package main

import "fmt"
import "math/rand"
import "time"

func main() {
   rand.Seed(int64(time.Now().UnixNano()))
   fmt.Println(rand.Int())
}

这样,每次运行时候的随机种子不一样,生成的随机数也就不一样了。

真随机数

真正的随机数是使用物理现象产生的:比如掷钱币、骰子、转轮、使用电子元件的噪音、核裂变等等,这样的随机数发生器叫做物理性随机数发生器,它们的缺点是技术要求比较高。

通常情况下使用时间戳生成的随机数就可以满足要求。但是,伪随机数其实是有周期的。这就意味着在高安全要求下的身份验证、加密等情况使用伪随机数有风险。

所以一般企业对产品的加密秘钥的生成必须采用真随机数生成器,这样才能保证万无一失,杜绝了被破解的可能性。

那么Golang中使用真随机数的方法是使用crypto/rand

package main

import (
	"crypto/rand"
	"fmt"
	"math/big"
)

func main() {
	// 生成 20 个 [0, 100) 范围的真随机数。
	for i := 0; i < 20; i++ {
		result, _ := rand.Int(rand.Reader, big.NewInt(100))
		fmt.Println(result)
	}
}

按照源文件,这些数据来自于每台机器:

// Package rand implements a cryptographically secure
// pseudorandom number generator.
package rand

import "io"

// Reader is a global, shared instance of a cryptographically
// strong pseudo-random generator.
//
// On Linux, Reader uses getrandom(2) if available, /dev/urandom otherwise.
// On OpenBSD, Reader uses getentropy(2).
// On other Unix-like systems, Reader reads from /dev/urandom.
// On Windows systems, Reader uses the CryptGenRandom API.

以Linux为例,优先调用getrandom(2),其实就是/dev/random优先。与/dev/urandom两个文件,他们产生随机数的原理其实是差不多的,本质相同:都是利用当前系统的熵池来计算出固定一定数量的随机比特,然后将这些比特作为字节流返回。

熵池就是当前系统的环境噪音,熵指的是一个系统的混乱程度,系统噪音可以通过很多参数来评估,如内存的使用,文件的使用量,不同类型的进程数量等等。

简单来说,这个随机数的随机种子为(进程数+内存占用长度+时间戳)(只是举例解释,并非实际算法),而系统中存在多少线程完全就是随机的,无周期的。

但要注意,通过这种方式,要比math.rand慢大约10倍