Math.random() 在中奖概率计算中的安全问题

Math.random() 与 crypto.getRandomValues() 的区别及使用场景

问题

使用 Math.random() 计算中奖概率是否存在安全风险?什么场景下应该使用 crypto.getRandomValues()

解答

Math.random() 的适用场景

对于普通的随机需求,Math.random() 完全够用:

生成随机 ID:

document.body.id = ('_' + Math.random()).replace('0.', '');

随机排序:

[1, 2, 3, 4, 5].sort(_ => Math.random() - .5);

Math.random() 的安全风险

Math.random() 不安全的原因与”伪随机”无关(getRandomValues() 也是伪随机),而是其实现机制导致的:

底层实现机制:

  • V8 引擎使用 xorshift128+ 算法生成随机数
  • 为了性能,一次生成 64 个随机数并缓存
  • 算法公开且源码可见,可被其他语言模拟

安全隐患:

攻击者如果获知当前随机生成器的状态,就能推算出缓存中的所有随机数。在抽奖、加密等场景中,这会导致严重的安全问题。

使用 crypto.getRandomValues()

基本用法:

let randNumber = self.crypto.getRandomValues(new Uint32Array(1))[0];
// 返回一个随机整数,通常 10 位
console.log(randNumber);

语法:

crypto.getRandomValues(typedArray)

支持的类型数组:Int8ArrayUint8ArrayInt16ArrayUint16ArrayInt32ArrayUint32Array

优化封装:

Math.randomValue = function () {
    return self.crypto.getRandomValues(new Uint32Array(1))[0];
};

getRandomValues() 的特点

更安全的随机源:

  • 使用系统层面的无序源(部分硬件自带随机种子)
  • 实时生成,无缓存机制
  • 不同浏览器实现可能不同

性能考虑:

由于实时生成,性能比 Math.random() 差。高并发场景下,如果随机数仅用于普通随机(非安全相关),应优先使用 Math.random()

使用建议

必须使用 getRandomValues() 的场景:

  • 生成密钥、token
  • 抽奖活动
  • 涉及金钱的随机计算
  • Node.js 服务端加密操作

可以使用 Math.random() 的场景:

  • 随机 ID 生成
  • 数组随机排序
  • UI 动画随机效果
  • 其他与安全无关的随机需求

关键点

  • Math.random() 使用缓存机制,算法公开可预测,不适合安全场景
  • crypto.getRandomValues() 实时生成随机数,使用系统级随机源,更安全但性能较差
  • 涉及金钱、加密、抽奖等场景必须使用 getRandomValues()
  • 普通随机需求(如随机 ID、排序)使用 Math.random() 即可
  • 前端几乎不存在高并发问题,可以放心使用 getRandomValues()