寻求专家建议:改进浏览器Math.random()的加密安全随机数方案可行性
嘿,这个问题问到点子上了——加密安全随机数的细节很容易踩坑,我来帮你理清楚:
这个思路本身是完全可行的,甚至是推荐的做法(当你需要全局替换不安全的Math.random()时)。但关键在于转换方式要正确,因为crypto.getRandomValues生成的是整数数组,而原生Math.random()返回的是[0,1)区间的浮点数。
正确的实现应该是这样的:
if (window.crypto && window.crypto.getRandomValues) { Math.random = () => { // 生成一个32位无符号整数,范围是0到2^32 - 1 const randomUint32 = new Uint32Array(1); window.crypto.getRandomValues(randomUint32); // 转换为[0,1)的浮点数:除以2^32,保证均匀分布 return randomUint32[0] / 2**32; }; }
这种方式能完整保留crypto.getRandomValues提供的加密安全熵,生成的浮点数也是均匀分布的,完全可以用于加密场景。
先看场景:如果你用的是Uint32Array(推荐),生成的数值本身就是非负的(0到2^32-1),这时候Math.abs()完全是多余的操作——不会增加或减少随机性,因为输入和输出完全一致。
但如果错误地使用了Int32Array,生成的数值可能是负数(范围是-231到231-1),这时候Math.abs()会把负数转成正数,但要注意一个极端情况:Int32Array的最小值是-2147483648,对它取Math.abs()会得到2147483648(JS的Number是64位浮点数,不会溢出),这个值的熵其实没有丢失,只是如果转回Int32Array会有问题,但直接用来转换浮点数的话,也不会大幅降低随机性。不过总的来说,用Uint32Array的话,Math.abs()完全没必要,也不会有负面影响。
绝对不能用Math.sin()来处理加密安全随机数!这会直接毁掉随机性,导致输出不再适合加密场景,原因有两个:
- 周期性:
sin(x) = sin(x + 2kπ)(k为整数),无数个不同的随机输入会得到完全相同的输出,直接丢失大量熵。 - 非双射:比如
sin(x) = sin(π - x),不同的输入对应同一个输出,进一步减少了输出的唯一性。
这样的输出不再是均匀分布,熵值大幅降低,很容易被攻击者预测或暴力破解,绝对不能用于加密场景。
内容的提问来源于stack exchange,提问作者Shawn Whinnery

