这篇文章发布于 2021年12月20日,星期一,23:10,归类于 JS API。 阅读 23759 次, 今日 15 次 13 条评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10241 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。
一、引言
我们日常开发经常会用到随机数,基本上我接触下来,都是使用 Math.random()
生成的。
例如生成随机ID:
document.body.id = ('_' + Math.random()).replace('0.', '');
请问这样实现有没有问题?
回答:没有问题。
例如随机排序:
[1, 2, 3, 4, 5].sort(_ => Math.random() - .5);
请问这样实现有没有问题?
回答:没有问题。
但是,如果你希望实现加密操作,例如生成密钥,尤其是在 Node.js 服务层,则 Math.random()
就有问题了,会有潜在的安全风险,需要使用 crypto.getRandomValues()
方法。
哦?安全风险?还有个 getRandomValues()
方法?
不急,我们慢慢聊。
二、Math.random的安全风险
提到 Math.random()
的安全风险,有开发人员会说因为 Math.random()
返回的是伪随机数。
这个解释似是而非,和伪随机数没有关系,getRandomValues()
方法返回的也是伪随机数。
还有人说因为 Math.random()
返回的随机值范围不是均匀的,这个回答就不是似是而非了,而是大错特错。
例如我运行个2万次 Math.random()
方法的分布图是下面这样的(实时Canvas绘制,外站无效果,点击重新绘制):
可以看到从左到右基本上是均匀的。
那究竟为何是不安全的呢?
这个就要讲讲 Math.random()
方法的底层实现了,这里有一篇文章有深入介绍,我简述下其中的要点。
Math.random()
函数返回一个范围0-1的伪随机浮点数,其在 V8 中的实现原理是这样的:
为了保证足够的性能,Math.random()
随机数并不是实时生成的,而是直接生成一组随机数(64个),并放在缓存中。
当这一组随机数取完之后再重新生成一批,放在缓存中。
由于 Math.random()
的底层算法是公开的(xorshift128+ 算法),V8 源码可见,因此,是可以使用其他语言模拟的,这就导致,如果攻击者知道了当前随机生成器的状态,那就可以知道缓存中的所有随机数,那就很容易匹配与破解。
例如抽奖活动,使用 Math.random()
进行随机,那么就可以估算出一段时间内所有的中奖结果,从而带来非常严重且致命的损失。
此时应该使用 getRandomValues()
方法。
//zxx: 如果你看到这段文字,说明你现在访问是不是原文站点,更好的阅读体验在这里:https://www.zhangxinxu.com/wordpress/?p=10241(作者张鑫旭)
三、了解getRandomValues方法
Crypto.getRandomValues()
方法返回的也是伪随机数,不是真随机,按照 MDN 的说法,是为了性能考虑,没有使用真随机。
实际上,按照我的认识,所有可以使用算法生成的随机数都可以看成是伪随机数,真随机数应该是存在自然界,例如粒子的起伏,声音的噪点,分子的分布等。
约翰·冯·诺伊曼(玩笑话):任何使用算术方法生成随机数的人,都是有罪的
和 Math.random()
方法的区别在于,getRandomValues()
方法的随机种子生成器更加的无序,例如系统层面的无序源(有些硬件自带随机种子)。
然后不同浏览器下 getRandomValues()
方法生成的随机数可能是有区别的。
以及 getRandomValues()
方法的底层实现是没有缓存的,随机数都是实时生成的,因此,性能上是要比 Math.random()
差的,因此,如果是高并发的场景,同时随机数仅仅是用做随机,与安全和金钱不相关,请使用 Math.random()
而不是 getRandomValues()
。
就 Web 前端而言,必须要使用 getRandomValues()
方法的场景很少,不过由于纯前端几乎不存在所谓的高并发,因此,你使用 getRandomValues()
方法也是可以的,有装逼的作用。
语法和使用
使用示意(下面代码 self 不太了解可以参见此文“了解全局作用域self”):
let randNumber = self.crypto.getRandomValues(new Uint32Array(1))[0]; // 一串随机整数,通常10位 console.log(randNumber);
语法为:
crypto.getRandomValues(typedArray)
支持的参数 typedArray
表示整数型的类型数组,包括:Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array 或者 Uint32Array。
返回值回是所有被替换为随机数的新的数组。
不过 getRandomValues()
方法名称有些长,不利于记忆和敏捷使用,我们可以改造下,例如:
Math.randomValue = function () { return self.crypto.getRandomValues(new Uint32Array(1))[0]; };
这样我们就可以使用 Math.randomValue()
方法返回足够安全的随机值了。
IE浏览器
在 IE11 浏览器下需要添加 ms 私有前缀:
window.msCrypto.getRandomValues()
IE10 不支持 getRandomValues()
方法。
四、Crypto对象与其他随机值
UUID生成
除了生成随机数,Crypto对象还可以用来生成字符长度为36的 UUID (Universally Unique Identifier的缩写,表示唯一通用标识符)。
例如:
let uuid = self.crypto.randomUUID(); console.log(uuid); // 示意输出:2433df46-d77f-4eb9-bbdd-4cd99361fe08
不过这个 API 是今年才支持的(Chrome 92+),还比较新,大家先了解下,等明年这个时候,差不多可以在一些前沿项目中尝试使用了。
crypto.subtle对象
crypto
对象还支持一个名为 subtle
的属性,目前唯一的属性,返回的属性值是一个对象,称为 SubtleCrypto 对象,包含大量的方法,可以用来生成各种签名和密钥,所有方法均返回 Promise,包括:
- SubtleCrypto.encrypt()
- SubtleCrypto.decrypt()
- SubtleCrypto.sign()
- SubtleCrypto.verify()
- SubtleCrypto.digest()
- SubtleCrypto.generateKey()
- SubtleCrypto.deriveKey()
- SubtleCrypto.deriveBits()
- SubtleCrypto.importKey()
- SubtleCrypto.exportKey()
- SubtleCrypto.wrapKey()
- SubtleCrypto.unwrapKey()
由于这些方法非本文重点,以及我们日常开发很少会用到,所以不展开介绍,有兴趣可以去 MDN 文档查看。
五、结语
我们日常开发开始推荐使用 Math.random()
方法,高性能且实用,但是如果我们的随机值与加密相关,或者涉及到金钱等安全性要求非常高的场景,务必使用 getRandomValues()
方法。
好,以上就是本文的全部内容。
是不是学到了不少知识呢,那还等什么,赶快给你的小伙伴吧。
本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=10241
(本篇完)
- 瞎折腾,使用JS让中文内容莫名其妙、狗屁不通 (0.316)
- 不借助Echarts等图形框架原生JS快速实现折线图效果 (0.237)
- 查漏补缺,我仍未知道的HTML nonce和popover属性 (0.237)
- “蝉原则”与CSS3随机多背景随机圆角等效果 (0.158)
- 了解JS中的全局对象window.self和全局作用域self (0.158)
- 原来浏览器原生支持JS Base64编码解码 (0.158)
- JS Intl对象完整简介及在中文中的应用 (0.158)
- Stylus-NodeJS下构建更富表现力/动态/健壮的CSS (0.053)
- 高富帅seajs使用示例及spm合并压缩工具露脸 (0.053)
- JS一般般的网页重构可以使用Node.js做些什么 (0.053)
- windows系统下批量删除OS X系统.DS_Store文件 (RANDOM - 0.053)
既然随机存在问题,那我们使用双重随机,是否能规避问题呢?比如我们随机产生一个循环次数,然后在循环里取随机数,这样取出来的随机数,就没有规律了呀?
let random = 0;
for(let i=0;i<Math.ceil(Math.random()*1000);i++){random = Math.random()}
本来random的机制就是为了提高性能,整个双重随机不是本末倒置嘛
貌似没用,第一前端js代码基本没隐私, random函数本文提了,会被模拟,那么你在这个情况下做什么,都是透明的, 并且都可以被模拟,那就存在隐患,加循环只是在不安全的锁上,加了另一层不安全的锁.
有好多地方写成了getRandomValue (少了 s), 特别是这个地方, 直接复制到控制台就报错啦…
https://www.zhangxinxu.com/wordpress/2021/12/js-getrandomvalue-math-random/comment-page-1/#:~:text=crypto.getRandomValue()
感谢反馈~
【如果攻击者知道了当前随机生成器的状态】
那么,如何才能知道当前随机生成器的状态呢
哈哈,你想攻击别人啊
设法摇出几千个随机数。
网上有文章预测过IE的(IE当年用C库的srand())
V8最初用密码学的随机数作Math.random,引起随机数分布不好看,出过事。
我说瞎话了。检查文中链接。
作为一个八卦人必须问下出了什么事
同问
老师,文章里的最前面的随机算法案例是有问题的,这个在概率上不是随机的