深入 zxcvbn Go 移植版:基于模式匹配的密码强度评估原理与接入实战
【免费下载链接】opencloud🌤️ OpenCloud is the open source platform for file management, sharing and collaboration. Simple and sovereign.项目地址: https://gitcode.com/GitHub_Trending/op/opencloud
导读
本文围绕当前仓库中随附的 vendor/github.com/trustelem/zxcvbn(Dropbox zxcvbn 的 Go 移植版)展开,系统讲解它如何借鉴真实密码破解器的思路,通过模式匹配与保守估算来评估密码强度:识别 3 万个常见密码、美国人口普查常见人名与姓氏、来自 Wikipedia 及美剧电影的高频英文词,以及日期、重复(aaa)、序列(abcd)、键盘轨迹(qwertyuiop)与 l33t 变体等常见模式。读完本文,你将掌握该库的完整 API、八大匹配器的工作机制、猜测数到 0–4 分值的换算规则、四种攻击场景的时间估算方法,以及它在当前仓库中的依赖形态与接入方式。
一、什么是 zxcvbn 及其 Go 移植版
zxcvbn 最初由 Dropbox 以 CoffeeScript 编写,核心理念是“像密码破解器一样思考”:不是简单看密码长度和字符种类,而是把密码拆解为多个可被攻击者命中的“模式”,再为每个模式估算猜测次数,最终给出保守的强度结论。当前仓库中随附的 README 明确说明,该 Go 移植版具有以下特点:
- 识别面广:通过模式匹配与保守估算,识别并加权约 3 万条常见密码、美国人口普查数据中的常见名字与姓氏、来自 Wikipedia 与美国影视作品的高频英文词,以及日期、重复(如
aaa)、序列(如abcd)、键盘模式(如qwertyuiop)和 l33t 语言(如p@ssw0rd)等常见模式。 - 目标与上游完全兼容:移植的目标是在使用同一组词典的前提下,与 Dropbox 上游 CoffeeScript 库给出相同结果(score、序列与猜测数)。为保证这一点,上游库的全部单元测试都被移植过来,并额外增加了更多测试。
- 当前状态:与 CoffeeScript 库的 v4.4.2 版本在评分、匹配序列与猜测数上实现 100% 兼容;唯一的已知缺口是 feedback 提示消息尚未实现。
在依赖层面,本仓库的 go.mod 将github.com/trustelem/zxcvbn以v1.0.1版本作为间接依赖引入(见 go.sum 中对应条目),源码整体随 vendor 目录随附,可在需要密码强度评估的 Go 服务中直接复用。
二、核心 API:PasswordStrength 与 Result
该库对外暴露的入口非常简洁。在 zxcvbn.go 中可以看到:
type Result struct { Guesses float64 Sequence []*match.Match Score int CalcTime float64 } func PasswordStrength(password string, userInputs []string) Result调用时只需传入待评估的密码和一个可选的“用户输入”列表(例如用户名、邮箱、姓名等,防止用户把个人信息拼进密码)。其内部流程为:
- UTF-8 校验:若密码包含非法 UTF-8 字节,直接返回零值结果——这意味着此类密码会被当作弱密码处理,避免非预期字符干扰匹配与评分。
- Omnimatch:调用
matching.Omnimatch(password, userInputs)收集全部候选匹配(见下一节)。 - 最优序列求解:调用
scoring.MostGuessableMatchSequence,在大量相互重叠的候选匹配中选出“猜测数最少”的无重叠组合。 - 汇总结果:
Guesses为最优序列的总猜测数,Score由猜测数换算为 0–4 分,Sequence为最终命中的匹配序列,CalcTime为本次计算的耗时(秒,保留 3 位小数)。
以返回的Result为核心,调用方可以自行决定展示形式:仅展示 0–4 分的强度条,或展开Sequence展示密码被拆解成了哪些片段(例如字典词、日期、键盘序列)。
三、八大匹配器:Omnimatch 的源码拆解
库的“模式识别”由 matching/omnimatch.go 中的Omnimatch统一编排。它会把所有匹配器依次跑一遍,收集各自命中的片段后统一排序:
matchers := []match.Matcher{ dictMatcher, reverseDictionnaryMatch{dm: dictMatcher}, l33tMatch{dm: dictMatcher, table: l33tTable}, spatialMatch{graphs: defaultGraphs}, repeatMatch{}, sequenceMatch{}, regexpMatch{regexes: defaultRegexpMatch}, dateMatch{}, }这八个匹配器分别负责:
| 匹配器 | 识别的模式 | 典型例子 |
|---|---|---|
| 字典匹配 | 词典中出现的词 | password、iloveyou |
| 反向字典匹配 | 倒写的词典词 | drowssap |
| l33t 匹配 | 字符替换变体 | p4ssw0rd、5ecret |
| 键盘空间匹配 | 键盘/小键盘上的连续轨迹 | qwertyuiop、1qaz2wsx |
| 重复匹配 | 连续重复 | aaa、abcabc |
| 序列匹配 | 递增/递减字符序列 | abcd、4321 |
| 正则匹配 | 近期年份等固定模式 | 1999、2016 |
| 日期匹配 | 常见日期格式 | 13/11/1992、1992-11-13 |
词典从哪里来
默认词典在 frequency/lists.go 中定义,共 6 类:passwords(常见密码)、english_wikipedia(维基百科高频词)、female_names、male_names(人口普查常见名字)、surnames(姓氏)以及us_tv_and_film(美剧与电影高频台词/词)。这些词表在启动时由 dictionary.go 中的buildRankedDict处理为“词 → 排名”的映射,排名越靠前(越小),说明该词越常见、对密码强度的拖累越大。
用户输入如何参与
Omnimatch的第一步,是调用dictMatcher.withDict("user_inputs", buildRankedDict(userInputs))把调用者传入的userInputs临时注册为一个额外的词典。也就是说,用户名、昵称、邮箱等个人词也会像常见密码一样被识别并扣分——这正是 zxcvbn 比简单规则更能反映真实风险的原因之一。
l33t 替换表与键盘图
l33t 匹配依赖一张“替换字符表”(同样定义在 omnimatch.go 中),例如a→4/@、e→3、s→$/5、t→+/7、o→0、i→1/!/|等。键盘匹配则使用 adjacency/graphs.go 生成的 qwerty、dvorak、keypad、mac_keypad 四张邻接图,记录每个键位相邻的键,从而识别诸如qwerty、asdfgh这类“沿着键盘画线”的密码。
四、猜测数估算:每种模式如何“计价”
每种模式命中后,scoring/guesses.go 中的EstimateGuesses会按模式类型分别估算猜测数:
- 字典词:基础猜测数 = 词在词典中的排名,再乘以大小写变体数、l33t 变体数(倒写再乘 2)。例如纯小写词大小写变体为 1,句首大写为 2,混合大小写则按组合数
C(u+l, i)计算。 - 暴力(bruteforce):
10^长度(BruteforceCardinality = 10),同时保证其猜测数至少比子匹配的最小猜测数大 1,从而让更“有意义”的模式优先覆盖同一段文本。 - 键盘轨迹:根据轨迹长度、转向次数、起始键数量与平均度数估算,若含 Shift 键还会再乘一个变体系数。
- 重复:基础模式的猜测数 × 重复次数。
- 序列:以首字符为基准赋基础值(
a/A/z/Z/0/1/9开头仅 4,数字开头 10,其余 26),降序序列再 ×2,最后乘以序列长度。 - 年份正则:
|当前年份 - 年份|与最小年份空间MinYearSpace = 20取较大者。 - 日期:
年份空间 × 365,带分隔符再 ×4(约 4 种常见分隔符选择)。
关键常量(同样在 guesses.go 中):
MinSubmatchGuessesSingleChar = 10:单字符子匹配的最小猜测数;MinSubmatchGuessesMultiChar = 50:多字符子匹配的最小猜测数;MinGuessesBeforeGrowingSequence = 10000:最优序列长度惩罚基数。
五、从猜测数到 0–4 分:评分阈值
最终得分由guessesToScore将总猜测数映射到 0–4 五个等级(见 time_estimate.go,阈值带DELTA = 5的缓冲):
| 猜测数范围 | 得分 | 含义(源码注释) |
|---|---|---|
| < 1e3 + 5 | 0 | 风险密码:“too guessable” |
| < 1e6 + 5 | 1 | “very guessable”,仅能抵御限速的在线攻击 |
| < 1e8 + 5 | 2 | “somewhat guessable”,能抵御不限速的在线攻击 |
| < 1e10 + 5 | 3 | “safely unguessable”,在加盐慢哈希(bcrypt/scrypt/PBKDF2/argon 等)前提下可抵御离线攻击 |
| ≥ 1e10 + 5 | 4 | “very unguessable”,离线攻击下依然安全 |
由此可见,score 的语义与攻击场景强相关:得分 3 及以上才被认为可以在加盐慢哈希的假设下抵抗离线破解。
六、破解时间估算:四种攻击场景
estimateAttackTimes(time_estimate.go)会把总猜测数换算为四种攻击场景下的破解秒数,并给出人可读的显示文本:
CrackTimesSeconds["online_throttling_100_per_hour"] = guesses / (100 / 3600) CrackTimesSeconds["online_no_throttling_10_per_second"] = guesses / 10 CrackTimesSeconds["offline_slow_hashing_1e4_per_second"] = guesses / 1e4 CrackTimesSeconds["offline_fast_hashing_1e10_per_second"] = guesses / 1e10即:每小时限速 100 次的在线攻击、每秒 10 次的不限速在线攻击、每秒 1 万次的慢哈希离线攻击、每秒 1e10 次的快速哈希离线攻击。显示文本按秒→分钟→小时→天→月(31 天)→年(12 个月)→世纪逐级换算,例如“less than a second”“3 days”“centuries”。
七、核心算法:最优匹配序列的动态规划
一个密码往往会被多个匹配器重复命中、且片段相互重叠(例如drowssap2016既是反向字典词也可能是日期加字典词的组合)。zxcvbn 用 scoring/scoring.go 中的MostGuessableMatchSequence求解“猜测数最少的非重叠序列”,算法注释给出了其最小化目标函数:
g = l! * Π(m.guesses for m in sequence) + D^(l-1)其中:
- 乘积项
Π(m.guesses):攻击者要尝试该组合中每个模式,猜测数相乘; - 阶乘项
l!:l个模式可以有不同的排列顺序,需计及全部排列; - 长度惩罚
D^(l-1):D = MinGuessesBeforeGrowingSequence = 10000,近似刻画“攻击者会先尝试更短的序列”这一行为。
算法按密码前缀逐位推进(对每个结束位置 j 维护最优状态),并保证最终序列中不会出现两段相邻的 bruteforce 匹配(合并成一段更优),最后从末尾回溯(unwind)得到最优序列。这也是Result.Sequence的来源——你可以借此看到密码到底“输”在了哪一段。
八、兼容性承诺与已知限制
按 README 的说明:
- 完全兼容:在相同词典集合下,score、匹配序列与猜测数与 Dropbox CoffeeScript 库 v4.4.2 完全一致,上游的全部单元测试均已移植并另有扩充,可用于跨语言结果比对与回归验证。
- 缺失项:feedback 消息(例如“请添加更多单词、符号”等面向用户的改进建议)尚未实现。如果产品需要“给出改进建议”,需要自行在
Sequence基础上补充提示逻辑。
九、在当前仓库中的接入位置与建议
zxcvbn 的源码位于vendor/github.com/trustelem/zxcvbn/下,构成清晰的模块化结构:
- zxcvbn.go:入口与
Result定义; - matching/:八大匹配器;
- scoring/:猜测数估算与最优序列求解;
- time_estimate.go:攻击时间估算与得分换算;
- frequency/lists.go:六类内置词典;
- adjacency/:键盘邻接图。
接入时最简用法即zxcvbn.PasswordStrength(pwd, []string{username}),随后根据Score决定注册/修改密码策略的门槛(例如要求 ≥ 3),并可选地将CalcTime用于性能观测。需要提醒的是:
- 内置词典以英文环境为主(英文常见密码、美式姓名、维基百科英文词频),面向中文用户时建议把高频弱口令、常见拼音词加入
userInputs之外的自定义词典逻辑(如需扩展词典,可在dictionaryMatch的 rankedDictionaries 基础上扩展)。 - 评分 3 分及以上的“安全”结论以加盐慢哈希为前提,最终安全性仍取决于存储侧的哈希算法(bcrypt/scrypt/PBKDF2/argon 等)与加盐策略。
- 该库在本仓库目前作为间接依赖(见 go.mod 中
v1.0.1条目)随 vendor 提供,直接引用即用,无需额外拉取外部依赖。
结语
zxcvbn 的可贵之处在于它把“破解视角”编码进了算法:不信任人的直觉,而是枚举攻击者会尝试的所有模式,用保守的猜测数说话。通过本文对匹配器、评分阈值、攻击时间估算与最优序列动态规划的拆解,配合仓库内源码按图索骥,即可在 OpenCloud 这样的 Go 生态项目中落地一套可解释、可验证的密码强度评估能力。
【免费下载链接】opencloud🌤️ OpenCloud is the open source platform for file management, sharing and collaboration. Simple and sovereign.项目地址: https://gitcode.com/GitHub_Trending/op/opencloud
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考