那一行 JavaScript,可能把安全防線拱手讓人
有段時間我在折騰一個周末小項目:給登錄系統(tǒng)生成“安全令牌”。我順手就用了 Math.random()——夠快、夠簡單??墒牵@東西真能扛住涉及密碼、令牌、ID 之類的敏感場景嗎?
結(jié)論先說:不能。也是從那時候起,我認真補上了“密碼學(xué)安全隨機”這一課,體驗可以說是徹底換了血。
下面把為啥 Math.random() 不靠譜、以及如何用真·安全隨機完成同樣需求一股腦講給你,并給出可直接上手的代碼片段。
Math.random():快,但不安全
JavaScript 自帶的 Math.random() 用起來毫無阻礙,生成的是偽隨機數(shù)——做個抽獎動效、隨機顏色、小游戲都 OK;但凡扯到安全,它就不及格了。
// ? 非密碼學(xué)安全
const notSecure = Math.random();
console.log(notSecure); // 0.8394720958480349為什么?因為可預(yù)測。
這類 PRNG(偽隨機數(shù)發(fā)生器)基于算法與狀態(tài)演進,只要攻擊者掌握足夠上下文/輸出樣本,就可能推斷后續(xù)值。在 2025 年的攻防環(huán)境里,把口令、令牌押在它身上,等于送分題。
看起來“隨機”,本質(zhì)并不“不可預(yù)測”。
真·安全隨機,得具備什么?
密碼學(xué)安全隨機數(shù)(CSPRNG)要滿足:
- 不可預(yù)測:給你再多歷史輸出,也猜不到下一個;
 - 不可復(fù)現(xiàn):不存在“同起點重現(xiàn)同序列”的穩(wěn)態(tài);
 - 高熵:每一比特更像擲硬幣,無明顯模式/偏差。
 
瀏覽器端首選:Web Crypto API
前端要用安全隨機,首推內(nèi)置的 Web Crypto API。它直接調(diào)用系統(tǒng)級熵源(硬件噪聲、OS 隨機池等),生成真正不可預(yù)測的字節(jié)序列。
比如來個“安全密碼”:
function generatePassword(length = 12) {
  const lowercase = 'abcdefghijklmnopqrstuvwxyz';
  const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  const numbers   = '0123456789';
  const symbols   = '!@#$%^&*()_+-=[]{}|;:,.<>?';
  const allChars = lowercase + uppercase + numbers + symbols;
  const bytes = new Uint8Array(length);
  crypto.getRandomValues(bytes); // ? 密碼學(xué)安全
  return Array.from(bytes, b => allChars[b % allChars.length]).join('');
}
const password = generatePassword(16);
console.log(password); // 例如: "K9#mP2$vN8&qR5@z"思路很簡單:用 crypto.getRandomValues() 拿到安全隨機字節(jié),再把每個字節(jié)映射到你的字符集上。沒有規(guī)律、沒有復(fù)用、沒有套路。
后端就用:Node.js crypto
到服務(wù)端,直接抄起 Node.js 內(nèi)置的 crypto 模塊,API 更全、使用場景更廣(令牌、會話、ID、整數(shù)等)。
來個“安全令牌”:
const crypto = require('crypto');
function generateSecureToken(length = 32) {
  return crypto.randomBytes(length).toString('hex'); // ? 密碼學(xué)安全
}
console.log(generateSecureToken()); // 例如: "a3b9f7c2e1d8..."randomBytes() 直接取自操作系統(tǒng)的隨機池,足夠安全;十六進制編碼,落地到數(shù)據(jù)庫/HTTP 頭/URL 都很順滑。
為什么這件事非做不可?
把 Math.random() 用在密碼/令牌上,等于給攻擊者提供可預(yù)測的入口; 而 Web Crypto / Node crypto 產(chǎn)生的高熵不可預(yù)測結(jié)果,會讓你的認證/授權(quán)/會話安全硬起來。
一句話對照表:
Math.random():快、可預(yù)測 → 僅限非敏感(如 UI 隨機色、動畫、樣例數(shù)據(jù))- Web Crypto:瀏覽器端密碼學(xué)安全 → 客戶端密碼、OTP、ID
 - **Node 
crypto**:服務(wù)端密碼學(xué)安全 → 令牌、UUID、后端識別碼 
幾個即插即用的小例子
1)安全整數(shù) OTP(Web Crypto)
function generateSecureOTP(min = 100000, max = 999999) {
  const range = max - min + 1;
  // 4 字節(jié)足夠覆蓋 32 位無符號整數(shù)空間
  const buf = new Uint8Array(4);
  crypto.getRandomValues(buf);
  const n = new DataView(buf.buffer).getUint32(0, false); // big-endian/無關(guān)緊要
  return min + (n % range);
}
console.log(generateSecureOTP()); // 例如: 472819注意:如果場景對均勻性要求極嚴,可做拒絕采樣避免模偏差;一般 OTP 使用場景,這樣已足夠。
2)數(shù)據(jù)庫唯一 ID(Node.js)
const crypto = require('crypto');
function generateSecureUUID() {
  return crypto.randomUUID(); // Node 16.7+ / 14.17+ 提供
}
console.log(generateSecureUUID()); // "123e4567-e89b-12d3-a456-426614174000"什么時候用哪個?
- 只做可視化/演示/非安全:
Math.random()足矣 - 瀏覽器端涉及安全:Web Crypto(密碼、OTP、客戶端 ID)
 - 服務(wù)端一切安全相關(guān):Node 
crypto(令牌、會話、UUID、簽名前隨機) 
小坑提示(總會有的)
- Web Crypto 需要安全上下文(HTTPS 或 localhost)。
 crypto.randomUUID()需要較新 Node 版本(Node 14+ 已支持該 API,但推薦 LTS/更新版本)。- 需要跨端一致時,別混用兩套實現(xiàn);選定一端生成,另一端只做校驗。
 
結(jié)語
下次手滑寫出 Math.random() 前,先問自己一句:這是不是敏感場景?如果答案是“是”,立刻換成 Web Crypto 或 **Node crypto**。 一行對,一整條安全鏈路都跟著穩(wěn)。
你在項目里用過這些安全隨機接口嗎?有沒有踩過“可預(yù)測隨機”的坑?評論區(qū)聊聊你的實戰(zhàn)經(jīng)驗。















 
 
 




 
 
 
 