更安全的隨機數生成

LnEoi發表於2022-01-04

Math.random()

通常情況下我們使用Math.random()來生成偽隨機數,在大部分情況下可以很方便的使用。比如

生成混合隨機字元

Math.random().toString(36).substr(2);

生成指定範圍的隨機數

const randomNumber = (min, max) => Math.floor(Math.random() * (max - min) + min);

但如果涉及稍微嚴謹的一些場景中,Math.random()隨機分佈不夠平均,這時候可以使用瀏覽器提供的更安全的隨機數生成介面Crypto.getRandomValues()

Crypto.getRandomValues()

Crypto看著會比較陌生,這是瀏覽器提供了基本的密碼學的操作介面。

其中提供了一個隨機數生成方法getRandomValuesMath.random()的隨機數分佈不夠平均,雖然大部分情況下不會有衝突,但確實有隱患。而且getRandomValues相容性也還是不錯的。

Crypto.getRandomValues

生成隨機數首先要提供一個TypedArray物件,生成的隨機數會將其裡面的物件重寫。

window.crypto.getRandomValues(new Uint32Array(5))

Crypto.getRandomValues()直接使用還是比較原始需要自己再次處理資料,網上也有提供可直接用的生成程式碼:

生成[0, 1)之間的隨機數(與Math.random()一致)

const cryptoRand = () => {
    const randomBuffer = new Uint32Array(1);
    window.crypto.getRandomValues(randomBuffer);
    return ( randomBuffer[0] / (0xffffffff + 1) ); // 0xFFFFFFFF = uint32.MaxValue (+1 because Math.random is inclusive of 0, but not 1) 
}

生成指定範圍隨機數

const randomNumber = (min, max) => {
    const randomBuffer = new Uint32Array(1);
    window.crypto.getRandomValues(randomBuffer);
    const number = randomBuffer[0] / (0xffffffff + 1);
    return Math.floor(number * (max - min) + min);
}

生成ID

const generateId = (len) => {
    const typeArray = new Uint8Array((len || 40) / 2)
    window.crypto.getRandomValues(typeArray)
    return Array.from(typeArray, dec=>dec.toString(16).padStart(2, "0")).join('')
}

或是

const uuidv4 = () => ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));

需要注意的是,為了效能crypto.getRandomValues生成隨機數的時候並沒有真正的隨機數生成器,而是使用了有足夠熵的偽隨機數生成器。如果需要加密場景,就需要使用者自己提供足夠熵源的種子。

或者使用現成的隨機數生成庫nanoid

Nano ID

Nano ID是一個小巧、安全、URL友好、唯一的 JavaScript 字串ID生成器。支援瀏覽器、Node.js、React Native,以及提供了各種其他語言的版本。

安裝

npm install --save nanoid

安全的生成隨機數:

同步

import { nanoid } from 'nanoid'
model.id = nanoid() //=> "V1StGXR8_Z5jdHi6B-myT"

非同步

import { nanoid } from 'nanoid/async'
await nanoid()

如果更關心效能的話,可以降低安全性使用非安全的隨機數生成器

import { nanoid } from 'nanoid/non-secure'
const id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqLJ"

當然也可以自定義隨機字元和大小,具體的方法可以檢視文件。

相關資料

MDN - Math.random()

MDN - Crypto.getRandomValues()

Nano ID 文件

Math.random() 二三事

Random Number Generation in JavaScript

相關文章