rand 解析
随机数在开发的应用比较广泛,在不同的安全级别下应该采用不同的生成方案。
伪随机数
在人类“一眼看上去”是随机的,实际上是用算法产生的一列被认为具有随机性的数字,常常叫伪随机数。
在安全敏感度不高的情况下,生成伪随机数可以满足大部分需求。
LCG(线性同余生成器)
这是 Golang 生成伪随机数的方案,叫线性同余法。
直接说一下 Golang 的生成公式,如下:
1 | next = (seed * 48271) % 2147483647 |
其中,seed
是当前的种子(初始种子由调用者设置),next
是生成的下一个伪随机数。48271 和 2147483647 是预定义的常量,用于执行乘法和取模操作。
48271 是生成器定义的常数。
214748364 是 2 ^ 31 - 1 ,通过对它取模确定随机数的范围在 int32
的范围内。
Golang 伪随机数的代码实现
从一个例子开始
rand.Seed()
1 | r := rand.New(rand.NewSource(time.Now().Unix())) |
第一行,将现在时间的时间戳作为种子传入 rand.NewSource()
,然后将它返回的 Source
传给 rand.New
,rand.New
返回一个结构体指针 *Rand
rand.NewSource()
1 | // NewSource 返回一个新的带有给定值的伪随机源。 |
可以看到在 rand.NewSource
中,首先创建了一个名为 rng
的 rngSource
结构体实例。然后,使用 Seed
方法将给定的种子值传递给该实例,以初始化随机数源的状态。最后,通过返回 &rng
,将初始化后的随机数源返回。
其中,返回类型 Source
是一个接口类型。其定义如下
1 | type Source64 interface { |
Seed
关键的种子处理函数 Seed
具体实现如下:
1 | // Seed 使用提供的种子值将生成器初始化为确定状态。 |
seedrand
1 | // seed rng x[n+1] = 48271 * x[n] mod (2**31 - 1) |
rand.New
至于 rand.New
,作用就是将 rand.Source
得到的接口类型给到它,然后赋值并返回一个指针。
1 | func New(src Source) *Rand { |
第二行,传入需要生成随机数的范围最大值 max + 1,然后通过 rand.Intn
生成一个随机数。
rand.Intn
1 | // Intn 以 int 形式返回半开区间 [0,n) 中的非负伪随机数。 |
可以看到在 n 的大小在 int32
范围内时,会调用 Int31n
Int31n
的具体实现如下
1 | func (r *Rand) Int31n(n int32) int32 { |
生成随机数的核心时调用了 Int31
1 | // Int31 returns a non-negative pseudo-random 31-bit integer as an int32. |
可以看到是调用了 Int63
这个方法然后取它的高32位。
1 | // Int63 returns a non-negative pseudo-random 63-bit integer as an int64. |
如果是Int64
的范围,本质上也是调用 Uint64
这个方法
至此,伪随机数的生成过程总结如下:
- 通过
rand.NewSource
设置种子 rand.NewSource
将种子进行处理,并通过线性同余法的计算公式得到一个随机数数组vec
。- 调用生成随机数的函数,本质上是调用
rngSource
的Uint64()
方法,取vec
中 的两数和。
真随机数
根据密码学原理,同时满足随机数的随机性检验的三个标准的,可称为真随机数。
需要注意的是,真随机数的生成涉及到操作系统底层,对性能的影响较大,非必要不用。
Golang 真随机数的实现
使用及源码
1 | package main |
可以看到关键函数是 rand.Int
具体实现代码在 crypto/rand
下
1 | // Int 返回 [0,max) 范围内的随机数,如果 max < 0 则 panic |
每个操作系统下,对应的真随机数获取流程不太一致,大体差不多。
值得一提的是使用了 go 编译标签( build tag) 来实现不同操作系统不同操作流程。
Windows
在Windows系统中,crypto/rand
包使用 RtlGenRandom
函数来获取随机数。RtlGenRandom
是Windows提供的一个使用操作系统内部的随机数生成器来生成真随机数的函数。
Linux
在Linux系统中,crypto/rand
包使用 /dev/urandom
设备来获取随机数。/dev/urandom
是一个特殊的设备文件,通过读取该文件可以获取系统内核中的随机数池。