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倍
。