天天看点

CryptoJS vs UUID ? 聊聊 Web Cryptography 终极方案?

作者:前端进阶

大家好,很高兴又见面了,我是"前端‬进阶‬",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

CryptoJS vs UUID ? 聊聊 Web Cryptography 终极方案?

伪随机数:用确定性的算法计算出来自[0,1]均匀分布的随机数序列

1.什么是伪随机数?

伪随机数是用确定性的算法计算出来自[0,1]均匀分布的随机数序列。并不真正的随机,但具有类似于随机数的统计特征,如均匀性、独立性等。在计算伪随机数时,若使用的种子不变,那么伪随机数的数序也不变。

CryptoJS vs UUID ? 聊聊 Web Cryptography 终极方案?

什么是伪随机数?

伪随机数可以用计算机大量生成,在模拟研究中为了提高模拟效率,一般采用伪随机数代替真正的随机数。模拟中使用的一般是循环周期极长并能通过随机数检验的伪随机数,以保证计算结果的随机性。

2.前端如何生成伪随机数?

以熟悉的Math.random()为例,该函数其实并不随机,它是一个伪随机数生成器(Pseudo Random Number Generator,简称PRNG),当指定同一个random_seed启动时,它生成的随机数序列是一样的!比如下面的例子将生成同样的随机数序列:

// 示例代码来源:[V8 Deep Dives] Random Thoughts on Math.random(),https://dev.to/puzpuzpuz/v8-deep-dives-random-thoughts-on-math-random-2ci4
node --random_seed=42
Welcome to Node.js v14.17.3.
Type ".help" for more information.
> Math.random()
0.5254990606499601
> Math.random()
0.963056226312738

node --random_seed=42
Welcome to Node.js v14.17.3.
Type ".help" for more information.
> Math.random()
0.5254990606499601
> Math.random()
0.963056226312738           

因此,只要获取random_seed,就能预测Math.random()所返回的"随机序列",这是很可怕的事情。

虽然,获取random_seed并不是一件简单的事情,不过并非完全没有可能,因为random_seed不是随机的,而是依赖于一些内部状态,比如浏览器启动时间、某个变量的虚拟内存地址,这些内部状态是有迹可循。

2014年,Andriod版的Firefox就曾被人破解过Math.random()。

因此,在对安全性要求比较高的场景中,不要使用Math.random()。CVE中有多个安全漏洞是与Math.random()相关。

3.随机数的社区方案和浏览器方案

3.1 随机数社区方案CryptoJS

为了满足大家对安全加密API的需求,社区提供了很多解决方案,以CryptoJS最出名。

// 不同加密算法的调用方式
const hash = CryptoJS.MD5("Message"); 
const hash = CryptoJS.SHA1("Message"); 
const hash = CryptoJS.SHA256("Message"); 
const hash = CryptoJS.SHA512("Message"); 
const hash = CryptoJS.SHA3("Message");            

CryptoJS是标准、安全加密算法的 JavaScript 实现,是一个完善的标准、安全加密算法集合,使用最佳实践和模式在 JavaScript 中实现。 速度快,并且具有一致且简单的界面。

或者使用社区的uuid方案:

import { v4 as uuidv4 } from 'uuid';
uuidv4(); 
// 输出 '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'           

从NPM的下载量来看UUID在前端更火爆,周下载量达到了惊人的61556K:

CryptoJS vs UUID ? 聊聊 Web Cryptography 终极方案?

UUID vs crytoJS前端使用对比

但是,对于加密这种CPU密集型应用,纯JavaScript的方案存在比较严重的性能问题,且不够安全。

3.2 随机数浏览器方案Web Cryptography

2017年,W3C发布了Web Cryptography API,提供更加安全、性能更好的加密API。其中,crypto.getRandomValues() 用于生成更加安全的随机数,它是密码学安全伪随机数生成器(Cryptographically Secure Pseudo Random Number Generator,简称CSPRNG)。其实,CSPRNG也并非生成真正的随机数,只是它通过一些严格的密码学测试,可以认为是安全的。

crypto.randomUUID()是基于CSPRNG的,因此可以认为是安全的。其使用加密安全随机数生成器生成 v4 UUID。

Crypto.getRandomValues() 方法可以获得加密强度高的随机值, 作为参数给出的数组填充了随机数(在密码学意义上是随机的)。

为了保证足够的性能,实现没有使用真正的随机数生成器,而是伪随机数生成器,其种子值具有足够的熵。

getRandomValues() 是 Crypto 接口中唯一可以在不安全的上下文中使用的成员。

4.getRandomValues和randomUUID

4.1 getRandomValues方法

getRandomValues方法的语法如下:

getRandomValues(typedArray)           
  • 参数typedArray:基于整数的 TypedArray,包含:Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、BigInt64Array、BigUint64Array(但不是 Float32Array 或 Float64Array)。 数组中的所有元素都将被随机数覆盖。
  • 方法返回值:返回数组与 typedArray 相同,但其内容已替换为新生成的随机数。 请注意,typedArray 是直接修改的而不是拷贝。

具体调用示例如下:

const array = new Uint32Array(10);
self.crypto.getRandomValues(array);
// 获取getRandomValues方法
console.log("幸运数字:");
for (const num of array) {
  console.log(num);
}           

并非所有的浏览器版本都支持这个方法,具体支持情况如下图:

CryptoJS vs UUID ? 聊聊 Web Cryptography 终极方案?

getRandomValues浏览器支持情况

4.2 randomUUID

Crypto 接口的 randomUUID() 方法用于使用加密安全随机数生成器生成 v4 UUID。其调用语法如下:

randomUUID()           
  • 参数:无
  • 返回值:包含随机生成的 36 个字符长的 v4 UUID 的字符串。

具体调用方式如下:

let uuid = self.crypto.randomUUID();
// 获取randomUUID方法
console.log(uuid); 
// 返回值类似"36b8f84d-df4e-4d49-b662-bcde71a8764f"
           

并非所有的浏览器版本都支持这个方法,具体支持情况如下图:

CryptoJS vs UUID ? 聊聊 Web Cryptography 终极方案?

randomUUID的浏览器支持情况

4.3 注意事项

不要使用 getRandomValues() 来生成加密密钥,如果确实要生成,可以使用 generateKey方法代替。

getRandomValues方法无法保证在安全上下文中运行

Web 密码规范没有规定最低熵程度。 相反,用户代理被敦促在生成随机数时提供最好的熵,使用用户代理本身内置的定义明确、高效的伪随机数生成器,但使用从伪随机数的外部来源获取的值作为种子。例如 特定于平台的随机数函数、Unix /dev/urandom 设备或其他随机或伪随机数据源。

参考资料

https://baike.baidu.com/item/%E4%BC%AA%E9%9A%8F%E6%9C%BA%E6%95%B0/104358?fr=aladdin

https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues

https://developer.aliyun.com/article/787589

https://caniuse.com/?search=crypto

https://cryptojs.gitbook.io/docs/