使用JavaScript生成安全的随机密码 - 技术解析与实践
使用JavaScript生成安全的随机密码
最近我需要用JavaScript代码生成随机密码,但惊讶地发现很难找到正确的实现方法。Google、StackOverflow甚至ChatGPT提供的大多数方案都存在各种缺陷。
常见错误方案分析
错误示例1:使用不安全的Math.random()
/* 弱随机数生成器示例,禁止使用 */
var chars = "0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var passwordLength = 12;
var password = "";
for (var i = 0; i <= passwordLength; i++) {
var randomNumber = Math.floor(Math.random() * chars.length);
password += chars.substring(randomNumber, randomNumber +1);
}
MDN文档明确指出:Math.random()
不提供加密安全的随机数,任何安全相关场景都应使用Web Crypto API的window.crypto.getRandomValues()
方法。
错误示例2:浮点数舍入偏差
/* 存在浮点舍入偏差的示例,禁止使用 */
function generatePassword(length = 16) {
let generatedPassword = "";
const validChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.-{}+!\"#$%/()=?";
for (let i = 0; i < length; i++) {
let randomNumber = crypto.getRandomValues(new Uint32Array(1))[0];
randomNumber = randomNumber / 0x100000000;
randomNumber = Math.floor(randomNumber * validChars.length);
generatedPassword += validChars[randomNumber];
}
return generatedPassword;
}
此方案虽然使用了加密安全的随机数,但通过浮点数转换会引入不均匀分布问题。
错误示例3:模偏差问题
/* 存在模偏差的示例,禁止使用 */
var generatePassword = (
length = 20,
characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!@-#$'
) =>
Array.from(crypto.getRandomValues(new Uint32Array(length)))
.map((x) => characters[x % characters.length])
.join('')
当随机数范围不是字符集大小的整数倍时,会导致某些字符出现概率更高。
正确实现方案
拒绝采样法
function simplesecpw() {
const pwlen = 15;
const pwchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const limit = 256 - (256 % pwchars.length);
let passwd = "";
let randval;
for (let i = 0; i < pwlen; i++) {
do {
randval = window.crypto.getRandomValues(new Uint8Array(1))[0];
} while (randval >= limit);
passwd += pwchars[randval % pwchars.length];
}
return passwd;
}
该方案具有三个关键特性:
- 使用加密安全的
crypto.getRandomValues()
- 完全避免浮点数运算
- 通过拒绝采样消除模偏差
在线演示可在https://password.hboeck.de/体验,代码已开源发布在GitHub。
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
公众号二维码