拥有唯一标识符是许多前端应用程序的直接述求。 通用唯一标识符(Universally Unique Identifiers )是 128 位数据,显示为 32 位十六进制数字。 uuid 解决了生成唯一 ID 以用于实际数据生成的问题,其依赖于随机或使用一些输入数据作为种子。
然而,确保唯一性本身就是一个挑战。 比如:如何确保 ID 唯一? 即使唯一,还要确保任何两个 ID 之间没有相关性? 这需要在 唯一性 和 随机性 之间有一个权衡,也是不同 uuid 生成器、不同版本生成器尝试解决的问题。

本文将围绕几种常见的不同 UUID 的生成方式展开,如 UUID、Nano ID 和 Crypto.randomUUID。
1. 什么是伪随机数伪随机数 是用确定性的算法计算出来自 [0,1] 均匀分布的随机数序列 ,并非真正的随机,但具有类似于随机数的统计特征,如 均匀性、独立性 等。在计算伪随机数时,若使用的种子不变,那么伪随机数的数序也不变。

伪随机数可以用计算机大量生成,在模拟研究中为了提高模拟效率,一般采用伪随机数代替真正的随机数。模拟中使用的一般是 循环周期极长并能通过随机数检验 的伪随机数,以保证计算结果的随机性。
以熟悉的 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-2ci4node --random_seed=42Welcome to Node.js v14.17.3.Type ".help" for more information.> Math.random()> 0.5254990606499601> Math.random()> 0.963056226312738node --random_seed=42Welcome 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(Common Vulnerabilities and Exposures) 中有多个安全漏洞是与 Math.random() 相关。
2. Crypto.randomUUID 生成密码学安全的伪随机数对于加密这种 CPU 密集型应用 ,纯 JavaScript 的方案存在比较严重的性能问题,且不够安全。2017 年,W3C 发布了 Web Cryptography API,其提供了更加安全、性

其中,crypto.getRandomValues() 用于生成更加安全的随机数,它是 密码学安全伪随机数生成器(Cryptographically Secure Pseudo Random Number Generator,简称 CSPRNG)。其实,CSPRNG 也并非生成真正的随机数,只是它通过一些 严格的密码学测试,可以认为是安全的。
crypto.randomUUID() 是基于 `CSPRNG` 的,因此可以认为是安全的。其使用加密安全随机数生成器生成 v4 UUID。
Crypto.getRandomValues() 方法可以获得加密强度高的随机值,作为参数给出的数组填充了随机数(在密码学意义上是随机的)。同时,为了保证足够的性能,其没有使用真正的随机数生成器,而是 伪随机数生成器,其种子值具有足够的熵。
getRandomValues() 是 Crypto 接口中唯一可以在不安全的上下文中使用的成员。可以通过下面的方法调用 randomUUID、getRandomValues 方法:
const array = new Uint32Array(10);self.crypto.getRandomValues(array);// 获取 getRandomValues 方法console.log('幸运数字:');for (const num of array) { console.log(num);}let uuid = self.crypto.randomUUID();// 获取 randomUUID 方法console.log(uuid);// 返回值类似 "36b8f84d-df4e-4d49-b662-bcde71a8764f"需要注意的是:不要使用 getRandomValues() 来生成加密密钥,如果确实要生成,可以使用 generateKey 方法代替。因为,getRandomValues 方法无法保证在安全上下文中运行。
Web 密码规范没有规定最低熵程度。 相反,用户代理被敦促在生成随机数时提供最好的熵,使用用户代理本身内置的定义明确、高效的伪随机数生成器,但 使用从伪随机数的外部来源获取的值作为种子。例如:特定于平台的随机数函数、Unix /dev/urandom 设备 或 其他随机或伪随机数据源。
3. 除了 CSPRNG 还有真随机数生成器 TRNG真随机数生成器 (TRNG,True Random Number Generator) 使用非确定性源来产生随机性。大多数功能通过测量不可预测的自然过程来实现,包括 电离辐射活动的脉冲检测器、气体放电管 和 漏电电容器。
英特尔开发了一种商用芯片,该芯片通过开发 跨未驱动电阻器测量的电压来采样热噪声。TRNG 生成真随机数,通常通过 硬件方法生成。

TRNG 生成的随机数很难预测,因为 TRNG 是 基于物理源生成的。因此,TRNG 生成的随机数是一种安全的方法,因为生成相等的值很复杂。
有以下可能的随机性来源,只要小心谨慎,就可以在计算机上简单地使用这些来源来产生真正的随机序列。
声音和视频输入: 许多计算机都带有输入,这些输入将一些现实世界的模拟源数字化,包括来自 麦克风的声音或来自摄像头的视频输入。磁盘驱动器:由于混乱的空气湍流,磁盘驱动器的转速会有微小的随机波动,底层磁盘寻道时间仪表的扩展会产生一系列包含这种随机性的测量。复杂的伪随机数生成器随处可见,但 TRNG 可以使用物理或非物理噪声源。

当然,除了 TRNG 外,还有一个叫 内核熵池 的东西。熵池会收集来自 设备驱动程序 和其它来源的 环境噪音,例如:用户点击鼠标的时间间隔、硬件设备发生中断的时间 等等。理论上,熵池中的数据是完全随机的,可以产生真随机数序列。其和 TRNG 的不同之处在于,内核熵池还加入了用户自身的行为元素。
4. 随机数的社区方案和浏览器方案4.1 随机数社区方案 CryptoJS为了满足大家对安全加密 API 的需求,社区提供了很多解决方案,以 CryptoJS 最出名。
const CryptoJS = require('crypto-js');// 不同加密算法的调用方式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 中实现。 速度快,并且具有一致且简单的界面。
目前 CryptoJS 加密方案在 Github 上有超过 14.1K 的 star、2k 的 fork、 482k 的项目依赖它、周 NPM 平均下载量达到了 4510K。
4.2 UUIDRFC4122 UUID 有 v1、v3、v4、v5 不同的版本。 uuid v1 是通过使用 主机 MAC 地址和当前日期和时间的组合 生成的,这种保证唯一性的方式是以匿名(Anonymity)为代价的。
而 uuid v4 是随机生成的,没有内在逻辑,可能的组合数量非常多(2¹²⁸), 除非每秒生成数万亿个 ID,否则几乎不可能生成重复项。但是, 如果当前的应用程序是关键任务,则可以使用唯一性约束来避免 v4 冲突。
与 v1 或 v4 不同的是,uuid v5 是通过提供两条输入信息生成的,即输入字符串和命名空间 ,这两条输入信息被转换为一致的 uuid,这意味着输入字符串和命名空间的任何给定组合每次都会产生相同的 uuid。
使用 UUID 有以下明显特定:
完整 : 支持 RFC4122 版本 1、3、4 和 5 UUID;跨平台;安全:加密强随机值;小:零依赖,占用空间小;包括 uuid 命令行实用程序。可以通过如下命令快速导入使用 uuid 生成器:
import {v4 as uuidv4} from 'uuid';let uuid = uuidv4();console.log(uuid);// 输出字符串 '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'如果是 CommonJS 语法,切换为如下方式即可:
const {v4: uuidv4} = require('uuid');uuidv4();// ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'如果是 ESM 可以采用如下方式:
<script type="module"> import {v4 as uuidv4} from 'https://jspm.dev/uuid'; console.log(uuidv4()); // ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'</script>uuid 模块内置了不同的方法,比如:uuid.parse()、uuid.stringify() 、uuid.v1()、uuid.v3()、uuid.v4()、uuid.v5()、uuid.validate()、uuid.version() 用于不同的场景需要。
目前 uuid 加密方案在 Github 上有超过 13.2K 的 star、880+ 的 fork、 17478k 的项目依赖它、周 NPM 平均下载量达到了 86591K。
参考资料https://github.com/sharkdp/hyperfine
https://medium.com/@matynelawani/uuid-vs-crypto-randomuuid-vs-nanoid-313e18144d8c
https://github.com/brix/crypto-js
https://github.com/uuidjs/uuid
https://github.com/ai/nanoid
https://baike.baidu.com/item/伪随机数/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/
https://www.zhihu.com/question/450697903/answer/2986382565
https://pytorch.org/blog/torchcsprng-release-blog/
https://www.tutorialspoint.com/what-are-true-random-number-generation
https://www.mdpi.com/1424-8220/19/19/4130
https://www.youtube.com/watch?v=V-Eu9P_RPIA
https://samvartaka.github.io/cryptanalysis/2017/01/03/33c3-embedded-rngs