JavaScript中Math.random函数的伪随机性解析以及破解方法研究
字数 1464 2025-08-22 22:47:39
JavaScript中Math.random函数的伪随机性解析及破解方法研究
1. 随机性概念解析
1.1 什么是随机性
- 随机性指事件中明显缺乏可预测性
- 理论上,如果知道所有影响因素及其准确值,可以预测结果(如抛硬币)
- 计算机中的随机数实际上是"伪随机"的
1.2 伪随机数生成器(PRNG)
- 基于算法生成数字序列
- 需要一个初始值(种子/seed)
- 生成过程:用当前数字生成下一个数字,形成序列
- 序列会重复,重复的长度称为"周期",周期越大算法越好
- 应用场景:游戏(如角色初始位置生成),但不适用于密码学
2. JavaScript中的Math.random
2.1 基本特性
- 返回大于0且小于1的浮点伪随机数
- Chrome浏览器使用V8引擎实现该函数
- V8是开源的,由Google维护
2.2 V8实现细节
- 使用xorshift128+算法
- 源代码位置:
- GitHub: https://github.com/v8/v8/tree/7a4a6cc6a85650ee91344d0dbd2c53a8fa8dce04
- 官方博客: https://v8.dev/blog/math-random
3. xorshift128+算法分析
3.1 算法实现代码
static inline void XorShift128(uint64_t* state0, uint64_t* state1) {
uint64_t s1 = *state0;
uint64_t s0 = *state1;
*state0 = s0;
s1 ^= s1 << 23;
s1 ^= s1 >> 17;
s1 ^= s0;
s1 ^= s0 >> 26;
*state1 = s1;
}
3.2 算法特点
- 基于两个64位状态变量(state0和state1)
- 主要操作:位异或(^)和位移(<<, >>)
- 每次调用会更新这两个状态变量
4. 使用Z3求解器破解Math.random
4.1 Z3求解器简介
- 由微软开发的SMT求解器
- 可将问题表示为方程式并指定约束条件
- 自动求解满足条件的变量值
4.2 破解步骤
4.2.1 准备工作
-
收集Math.random生成的随机数序列(至少5个)
Array.from(Array(5), Math.random)示例序列:
sequence = [0.6199046082820001, 0.6623637813965961, 0.7190181683749095, 0.06169296721449724, 0.915799780594273] -
安装必要的Python库:z3, struct
4.2.2 实现破解脚本
#!/usr/bin/python3
import z3, struct, sys
# 1. 准备已知的随机数序列(逆序排列)
sequence = [0.6199046082820001, 0.6623637813965961,
0.7190181683749095, 0.06169296721449724,
0.915799780594273]
sequence = sequence[::-1]
# 2. 创建Z3求解器和状态变量
solver = z3.Solver()
se_state0, se_state1 = z3.BitVecs("se_state0 se_state1", 64)
# 3. 模拟xorshift128+算法
for i in range(len(sequence)):
se_s1 = se_state0
se_s0 = se_state1
se_state0 = se_s0
se_s1 ^= se_s1 << 23
se_s1 ^= z3.LShR(se_s1, 17) # 使用逻辑右移
se_s1 ^= se_s0
se_s1 ^= z3.LShR(se_s0, 26)
se_state1 = se_s1
# 4. 将随机数转换为64位表示并提取尾数
float_64 = struct.pack("d", sequence[i] + 1)
u_long_long_64 = struct.unpack("<Q", float_64)[0]
mantissa = u_long_long_64 & ((1 << 52) - 1)
# 5. 添加约束条件
solver.add(int(mantissa) == z3.LShR(se_state0, 12))
# 6. 检查并求解
if solver.check() == z3.sat:
model = solver.model()
states = {}
for state in model.decls():
states[state.__str__()] = model[state]
# 7. 预测下一个随机数
state0 = states["se_state0"].as_long()
u_long_long_64 = (state0 >> 12) | 0x3FF0000000000000
float_64 = struct.pack("<Q", u_long_long_64)
next_sequence = struct.unpack("d", float_64)[0]
next_sequence -= 1
print(next_sequence)
4.3 关键点说明
-
状态变量:使用两个64位变量(se_state0, se_state1)表示算法内部状态
-
算法模拟:完全按照V8的xorshift128+算法实现
-
浮点数处理:
- JavaScript的Math.random返回[0,1)范围内的双精度浮点数
- 使用struct模块处理浮点数的二进制表示
- 提取52位尾数作为约束条件
-
约束条件:
- 将已知随机数的尾数与算法状态的移位结果关联
- 使用逻辑右移(LShR)而非算术右移
-
预测下一个数:
- 求解得到当前状态后,计算下一个状态
- 将状态转换为符合Math.random输出格式的浮点数
5. 实际应用与限制
5.1 应用场景
- 预测游戏中的随机事件
- 分析依赖Math.random的安全机制
- 研究伪随机数生成器的特性
5.2 限制条件
- 需要连续获取多个(≥5)随机数样本
- 仅适用于使用xorshift128+算法的环境(如Chrome浏览器)
- 不适用于经过加密处理的随机数生成器
6. 防御措施
如果应用需要不可预测的随机数:
- 使用密码学安全的随机数生成器(如Web Crypto API)
- 不依赖Math.random进行安全相关的操作
- 考虑使用外部熵源增加随机性
7. 总结
本文详细解析了JavaScript中Math.random函数的实现原理,基于V8引擎的xorshift128+算法,并提供了使用Z3求解器破解该伪随机数生成器的方法。通过收集少量连续的随机数样本,可以重建算法内部状态并预测后续输出。这一技术揭示了伪随机数生成器在特定条件下的可预测性,强调了在安全敏感场景中使用适当随机数源的重要性。