密码生成器与强度检测
生成强随机密码、实时评估强度与熵值,附弱密码清单提示,本地运行零上传。
在线演示
0信息熵(bit)
—强度等级
—离线爆破估算
项目简介
密码是账号安全的第一道防线。本页提供两段可独立使用的代码:强随机密码生成(可指定长度与字符集)与强度评估(根据长度、字符种类与熵值给出等级与改进建议)。全部在浏览器本地计算,密码不会离开设备。
代码示例:强随机密码生成
js
function randomInt(n) {
const a = new Uint32Array(1);
crypto.getRandomValues(a);
return a[0] % n;
}
const SETS = {
lower: "abcdefghijklmnopqrstuvwxyz",
upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
digit: "0123456789",
symbol: "!@#$%^&*()-_=+[]{};:,.<>?"
};
function generatePassword(length, use = ["lower", "upper", "digit", "symbol"]) {
const pool = use.map(k => SETS[k]).join("");
if (!pool) throw new Error("empty charset");
const chars = [];
// 保证每种已选字符集至少出现一次,再随机补齐
for (const k of use) chars.push(SETS[k][randomInt(SETS[k].length)]);
while (chars.length < length) chars.push(pool[randomInt(pool.length)]);
// Fisher-Yates 洗牌,避免首字符规律
for (let i = chars.length - 1; i > 0; i--) {
const j = randomInt(i + 1);
[chars[i], chars[j]] = [chars[j], chars[i]];
}
return chars.join("");
}代码示例:强度评估与熵值
js
function entropyBits(pw) {
let poolSize = 0;
if (/[a-z]/.test(pw)) poolSize += 26;
if (/[A-Z]/.test(pw)) poolSize += 26;
if (/[0-9]/.test(pw)) poolSize += 10;
if (/[^a-zA-Z0-9]/.test(pw)) poolSize += 32;
return poolSize ? pw.length * Math.log2(poolSize) : 0;
}
function strengthLevel(pw) {
const bits = entropyBits(pw);
if (pw.length < 8 || bits < 45) return "weak";
if (bits < 70) return "medium";
if (bits < 100) return "strong";
return "excellent";
}
// 常用弱密码黑名单(示例,可扩充)
const WEAK = new Set(["123456", "password", "qwerty", "admin", "111111"]);
function isWeak(pw) {
const low = pw.toLowerCase();
return WEAK.has(low) || /(.)\1{3,}/.test(pw) || low.includes("123");
}操作步骤
- 在演示区设置长度与字符集,点击"生成密码",下方结果区一次输出 1/5/10 条候选;"复制第一条"可一键写入剪贴板。
- 生成过程中每类已勾选字符至少出现一次(拒绝采样保证无模偏差),"排除易混字符"可去掉 0 O 1 l I | 这些容易看错的字符。
- 把任意密码粘贴进"强度检测"框:实时给出信息熵(bit)、强度等级、离线爆破估算与具体改进建议;命中常见弱密码黑名单会直接标红提示。
- 对比测试:输入
abc123(命中黑名单)与MyDog$2024!atHome(约 100 bit)观察等级与条形图变化。
完整可运行工程
可直接运行Web Crypto,零依赖必须用 file:// 或 HTTPS 打开
运行环境:现代浏览器 + Node 18(可选,仅自测用)。crypto.getRandomValues 属于安全上下文 API,请直接双击 index.html(file://)或用 HTTPS 部署;用 http:// 明文域名打开时随机数接口不可用。下载 3 个文件放同一目录即可运行。
依赖清单:零第三方依赖(Web Crypto + DOM);Node 18+ 仅用于运行 README 里的自测命令,页面本身无需 Node。
password-generator/
├── index.html 界面(生成面板 + 强度检测面板)
├── app.js 安全随机生成 + 熵值/强度评估(已导出纯函数便于自测)
└── README.md 运行说明、依赖清单、最小自测命令与安全建议
index.html
生成面板(长度/字符集/排除易混)+ 强度检测面板,含深色模式样式。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>密码生成器与强度检测</title>
<style>
body { margin: 0; padding: 28px 18px; font-family: system-ui, "Segoe UI", sans-serif; background: #f6f7f8; color: #1a1d21; }
.wrap { max-width: 720px; margin: 0 auto; }
h1 { font-size: 1.55rem; margin: 0 0 6px; }
.sub { color: #5b636e; font-size: 0.9rem; margin: 0 0 22px; }
fieldset { border: 1px solid #e2e5ea; border-radius: 12px; padding: 16px 18px; background: #fff; margin: 0 0 18px; }
legend { font-size: 0.85rem; color: #5b636e; padding: 0 6px; }
.row { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; font-size: 0.88rem; }
input[type="text"], select { padding: 8px 11px; border: 1px solid #d6dae0; border-radius: 9px; font-size: 0.9rem; }
input[type="text"] { width: 100%; box-sizing: border-box; font-family: Consolas, monospace; }
button { padding: 9px 18px; border-radius: 10px; border: 1px solid #d6dae0; background: #fff; cursor: pointer; font-size: 0.88rem; }
button.primary { background: #1c6659; border-color: #1c6659; color: #fff; }
pre#out { background: #0d0f12; color: #d8dee9; padding: 14px 16px; border-radius: 10px; font-size: 0.85rem; min-height: 60px; overflow: auto; }
.bar { height: 8px; border-radius: 99px; background: #eceef1; overflow: hidden; margin: 14px 0; }
.bar > i { display: block; height: 100%; width: 0; background: #1c6659; }
ul { font-size: 0.85rem; color: #3c444e; line-height: 1.75; padding-left: 18px; }
@media (prefers-color-scheme: dark) {
body { background: #14161a; color: #e6e9ee; } .sub, legend { color: #9aa3b0; }
fieldset, button, input[type="text"], select { background: #1c1f25; color: #e6e9ee; border-color: #2a2f38; }
button.primary { background: #6fb3a8; color: #10130f; border-color: #6fb3a8; }
.bar { background: #22262d; } .bar > i { background: #6fb3a8; } ul { color: #b9c1cc; }
}
</style>
</head>
<body>
<div class="wrap">
<h1>密码生成器与强度检测</h1>
<p class="sub">随机数来自 crypto.getRandomValues,全部计算在本地完成,密码不会离开设备。</p>
<fieldset>
<legend>生成</legend>
<div class="row">
<label>长度 <input id="len" type="range" min="8" max="64" value="20" /> <b id="lenVal">20</b></label>
<label><input type="checkbox" id="upper" checked /> 大写</label>
<label><input type="checkbox" id="lower" checked /> 小写</label>
<label><input type="checkbox" id="digit" checked /> 数字</label>
<label><input type="checkbox" id="symbol" checked /> 符号</label>
<label><input type="checkbox" id="noSimilar" checked /> 排除易混</label>
<button class="primary" id="gen">生成</button>
</div>
<pre id="out"></pre>
</fieldset>
<fieldset>
<legend>强度检测</legend>
<input id="test" type="text" placeholder="粘贴密码,实时评估" autocomplete="off" spellcheck="false" />
<div class="bar"><i id="bar"></i></div>
<p id="summary">熵值 0 bit</p>
<ul id="advice"></ul>
</fieldset>
</div>
<script src="app.js"></script>
</body>
</html>app.js
安全随机(拒绝采样 + Fisher-Yates 洗牌)、熵值评估、黑名单与离线爆破估算;含 Node 导出分支。
js
/* 密码生成与强度评估:无依赖,直接运行
随机源使用 Web Crypto(crypto.getRandomValues),带拒绝采样消除模偏差 */
const SIMILAR = "0O1lI|`'\"";
const SETS = {
upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
lower: "abcdefghijklmnopqrstuvwxyz",
digit: "0123456789",
symbol: "!@#$%^&*()-_=+[]{};:,.?/~"
};
function buildPool(opts) {
let pool = "";
if (opts.upper) pool += SETS.upper;
if (opts.lower) pool += SETS.lower;
if (opts.digit) pool += SETS.digit;
if (opts.symbol) pool += SETS.symbol;
if (opts.noSimilar) pool = [...pool].filter((c) => !SIMILAR.includes(c)).join("");
return pool;
}
/* 无模偏差的随机下标:拒绝落在尾部不成整倍数的区间 */
function randomIndex(max) {
if (max <= 0) throw new Error("empty pool");
const limit = Math.floor(0x100000000 / max) * max;
const buf = new Uint32Array(1);
let v;
do { crypto.getRandomValues(buf); v = buf[0]; } while (v >= limit);
return v % max;
}
/* 保证每类已勾选字符至少出现一次,再整体洗牌 */
function generate(length, opts) {
const pool = buildPool(opts);
if (!pool) throw new Error("请至少选择一类字符");
const required = [];
["upper", "lower", "digit", "symbol"].forEach((k) => {
if (!opts[k]) return;
const chars = [...SETS[k]].filter((c) => !(opts.noSimilar && SIMILAR.includes(c)));
if (chars.length) required.push(chars[randomIndex(chars.length)]);
});
const out = required.slice(0, length);
while (out.length < length) out.push(pool[randomIndex(pool.length)]);
for (let i = out.length - 1; i > 0; i--) { /* Fisher-Yates 洗牌 */
const j = randomIndex(i + 1);
[out[i], out[j]] = [out[j], out[i]];
}
return out.join("");
}
const WEAK = ["password", "123456", "12345678", "qwerty", "111111", "abc123", "admin",
"letmein", "iloveyou", "welcome", "monkey", "dragon", "666666", "888888"];
/* 熵值(bit):按实际字符分布估计字符池大小 */
function entropy(pw) {
if (!pw) return 0;
let pool = 0;
if (/[a-z]/.test(pw)) pool += 26;
if (/[A-Z]/.test(pw)) pool += 26;
if (/[0-9]/.test(pw)) pool += 10;
if (/[^A-Za-z0-9]/.test(pw)) pool += 30;
const unique = new Set(pw).size;
if (unique <= 2) pool = Math.min(pool, 4); /* aaa111 这类模式 |
可猜空间远小于字符池 */
return Math.round(pw.length * Math.log2(Math.max(pool, 2)) * 10) / 10;
}
function evaluate(pw) {
const bits = entropy(pw);
const advices = [];
const lower = pw.toLowerCase();
const hitWeak = WEAK.some((w) => lower === w || (pw.length >= 6 && lower.includes(w)));
if (pw.length < 12) advices.push("长度不足 12 位:长度是抵抗暴力破解最有效的因素,建议 16 位以上。");
if (!/[A-Z]/.test(pw)) advices.push("缺少大写字母,字符池变小。");
if (!/[0-9]/.test(pw)) advices.push("缺少数字。");
if (!/[^A-Za-z0-9]/.test(pw)) advices.push("缺少符号,建议至少加入一个符号。");
if (/(.)\1{2,}/.test(pw)) advices.push("存在连续重复字符(如 aaa),会被字典规则优先命中。");
if (/^(?:\d+|[a-z]+|[A-Z]+)$/.test(pw)) advices.push("只由单一类型字符组成,模式过于规律。");
if (/(19|20)\d{2}/.test(pw)) advices.push("包含年份,属于社工字典常见组合。");
if (hitWeak) advices.push("命中常见弱密码/黑名单片段,必须更换。");
let level = "很弱", pct = 8;
if (hitWeak) { level = "已被泄露的弱密码"; pct = 5; }
else if (bits >= 90) { level = "极强"; pct = 100; }
else if (bits >= 70) { level = "强"; pct = 78; }
else if (bits >= 50) { level = "中等"; pct = 52; }
else if (bits >= 35) { level = "偏弱"; pct = 28; }
else { level = "很弱"; pct = 10; }
if (!advices.length) advices.push("未发现明显弱点。建议配合密码管理器保存,并为重要账号开启两步验证。");
/* 离线爆破估算:1e11 次/秒(高端 GPU 集群量级) */
const trials = Math.pow(2, bits) / 2;
const seconds = trials / 1e11;
return { bits, level, pct, advices, crack: humanTime(seconds) };
}
function humanTime(sec) {
if (sec < 1) return "瞬间";
const units = [["年", 31557600], ["天", 86400], ["小时", 3600], ["秒", 1]];
const out = [];
let rest = sec;
for (const [name, size] of units) {
if (rest >= size && out.length < 2) { out.push(Math.floor(rest / size) + " " + name); rest %= size; }
}
return out.join(" ") || "1 秒内";
}
/* ------------------------------ 界面绑定 ------------------------------ */
function opts() {
return {
upper: document.getElementById("upper").checked,
lower: document.getElementById("lower").checked,
digit: document.getElementById("digit").checked,
symbol: document.getElementById("symbol").checked,
noSimilar: document.getElementById("noSimilar").checked
};
}
function renderEval() {
const pw = document.getElementById("test").value;
const r = evaluate(pw);
document.getElementById("bar").style.width = r.pct + "%";
document.getElementById("summary").textContent =
pw ? `熵值 ${r.bits} bit · ${r.level} · 离线爆破约 ${r.crack}` : "请输入密码";
const ul = document.getElementById("advice");
ul.innerHTML = "";
r.advices.forEach((a) => { const li = document.createElement("li"); li.textContent = a; ul.appendChild(li); });
}
if (typeof document !== "undefined") {
document.addEventListener("DOMContentLoaded", () => {
const len = document.getElementById("len");
len.addEventListener("input", () => { document.getElementById("lenVal").textContent = len.value; });
document.getElementById("gen").addEventListener("click", () => {
document.getElementById("out").textContent = generate(Number(len.value), opts());
});
document.getElementById("test").addEventListener("input", renderEval);
document.getElementById("gen").click();
renderEval();
});
}
/* ---- 供 Node 最小自测使用(浏览器中该分支不执行) ---- */
if (typeof module !== "undefined" && module.exports) {
module.exports = {
SETS: SETS, SIMILAR: SIMILAR, buildPool: buildPool, randomIndex: randomIndex,
generate: generate, entropy: entropy, evaluate: evaluate
};
}
README.md
运行方式、依赖清单、最小自测命令与安全边界说明。
md
# 密码生成器与强度检测(password-generator)
零依赖单页工具:安全随机密码生成 + 实时强度评估。所有计算在浏览器本地完成。
## 运行
1. 双击 index.html 即可使用(无需构建、无需依赖)。
2. 生成使用 crypto.getRandomValues + 拒绝采样,避免 Math.random 的可预测性与取模偏差。
## 安全边界(务必了解)
- 阈值参考:熵值 >= 70 bit 视为"强",>= 90 bit 为"极强";低于 50 bit 不建议用于重要账号。
- 离线爆破估算按 1e11 次/秒(高端 GPU 集群量级)折算,仅用于比较密码之间强弱,不代表真实攻击时间。
- 生成页面本身不应通过 HTTP 明文访问(避免中间人窃取),建议本地打开或 HTTPS 部署。
- 强度评估不能发现所有风险(如撞库泄露、钓鱼),重要账号请配合密码管理器 + 两步验证。
## 依赖清单与最小自测
- 依赖清单:零第三方依赖(浏览器原生 Web Crypto + DOM);Node 18+ 可直接跑逻辑自测,不需要浏览器。
- 最小自测(复制本目录后执行,期望输出 `20 true 已被泄露的弱密码`):
```bash
node -e "const m=require('./app.js');const p=m.generate(20,{upper:true,lower:true,digit:true,symbol:true,noSimilar:true});console.log(p.length, m.entropy(p)>100, m.evaluate('123456').level)"
```
运行标注:
可直接运行(浏览器 Web Crypto + DOM,零依赖、无需构建)。需先改的配置项:无必改项;如需调整默认长度/字符集,改 index.html 中对应控件默认值即可。最小自测:index.html 打开即用;逻辑自测用 README.md 里的 node 命令(期望 20 true 已被泄露的弱密码)。来源参考
实现所依据的官方文档与标准(均可直接访问):