基于概率排序的智能密码字典生成器设计解析
做密码安全测试这几年我越来越觉得传统字典生成工具在实战中总是差口气。要么是纯暴力穷举生成的文件动辄几个TB跑起来效率低到怀疑人生要么是规则死板只会机械地把几个词库拼接在一起完全不懂人类设密码时的那些小心思。所谓智能密码猜测生成器核心思路就是先分析目标群体的密码习惯再用概率去驱动生成顺序把真正有可能命中的组合排在前面。我基于这个思路做了一款自己的工具取名为PrecisionPasswordGenerator不追求覆盖所有可能性只追求用最少的数据量、最短的时间产出最高命中率的候选密码集。这篇文章把整个设计思路、核心算法和实战参数都摊开来讲给同样在做安全测试的朋友一个可参考的样本。1. 项目定位与设计动机1.1 为什么通用字典生成器不够用市面上的密码猜测工具大致分成两派一派是纯数学意义上的排列组合穷举典型代表是按字符集和长度暴力枚举的生成器另一派是依赖现成大字典的套用工具简单把社工库里的常用密码拼在一起。前者的问题是产出量呈指数级爆炸一个8位纯小写字母的组合就有两千多亿种即便按每秒十亿次的GPU算力也要跑几分钟放在真实业务里这种效率根本无法接受。后者的问题是字典文件过于通用里面塞满了来自全球各地、各种语言、各个年代的泄露数据换到具体场景时命中率惨不忍睹。我做这个工具时定的第一个目标就是要把生成规模压缩到可控范围。传统思路动辄几GB的字典文件我只允许自己生成不超过200MB的候选集。因为在实际渗透测试中字典文件的大小直接决定了在线猜测能否在限定时段内完成。很多目标系统有登录频率限制、有验证码机制你根本没有时间把海量组合全部试一遍必须在有限次数内尽可能命中。第二个痛点是通用工具无法感知密码生成的文化背景。一个中国程序员的密码习惯和一个欧美用户完全不一样。国内用户喜欢用姓名拼音加生日、手机号加固定后缀、或者键盘顺序路径欧美用户则更倾向于单词加数字的组合。通用字典里这些结构混在一起互相稀释权重导致该优先尝试的高概率组合经常排在几十万条之后。1.2 工具要解决的核心场景PrecisionPasswordGenerator设计的核心理念用一个词概括就是概率优先。它不是一个单纯的字典生成器而是一个集模式分析、规则引擎、概率排序于一体的综合工具。它的定位是服务于三类具体场景第一类场景是有已知密码样本的定向审计。比如在授权渗透测试中你拿到了某个系统的密码哈希值或者通过钓鱼测试回收了一部分员工的密码习惯数据。这时候工具会从这些样本中自动提取模式特征比如8位字母加两位数字、首字母大写加生日等然后按照这些特征的分布比例去扩展生成新的候选密码。这种场景下命中率通常能到百分之二三十比纯字典工具高出一个数量级。第二类场景是完全没有任何先验信息的通用测试。工具内置了基于多年泄露数据统计得到的高频模式库和基础词表按统计概率从高到低生成初始字典。这个场景下追求的是在头一两百万条数据里覆盖尽可能多的常见密码让在线测试能快速见效。第三类是专项场景的定制生成。比如目标系统是某银行内部系统或者某学校的学生系统可以手动指定一些特定词根比如系统名称缩写、域名关键词、行业术语等工具会围绕这些词根按规则库自动衍生组合。1.3 与传统方案的关键差异对比维度传统字典生成器PrecisionPasswordGenerator生成策略字符集穷举或字典拼接模式识别加概率排序产出规模数百GB到数TB控制在一两百MB以内排序逻辑字母序或固定规则按命中概率从高到低可定制性需要手写复杂脚本规则文件加命令行参数场景适配全球通用无偏好中文语境深度适配这个对比就能看出来了传统工具解决的是有没有的问题我做的这个工具解决的是先试哪个的问题。同样一次测试传统字典可能要在GB级数据里碰运气而概率排序后的字典可以在头几十万条就把命中率拉起来。2. 核心模块拆解2.1 密码模式分析引擎模式分析是整条生成链路的第一个环节也是决定后续所有步骤效率的关键。这个模块的输入可以是一批已知密码明文也可以是一批密码哈希值配合已破解结果使用。它要做的事情就是从不规则的密码样本中提取出结构化的模式特征。具体实现时我把密码模式拆成了几个维度来观察。第一个维度是长度分布直接统计样本中各种密码长度的出现频次确定后续生成的优先生成长度区间。第二个维度是字符类别序列把每个密码映射成一个结构串数字用d表示、小写字母用l表示、大写字母用u表示、特殊符号用s表示。比如wang2024就被映射成lll dddd而Wang2024则被映射成ulll s dddd。这里有个细节值得注意字符类别序列的粒度决定模式识别的准确性。如果只按字符类别粗分很容易把结构差异巨大的密码归成一类。所以我额外增加了一个内容层面的分层纯数字段如果长度大于等于四位继续判定它是不是年份19xx或20xx、是不是手机尾号、是不是递增递减序列字母段则继续判定它是否命中常用拼音库、英文单词库、姓名库或键盘路径库。这样一层层剥下来每个密码样本都会被打上若干个语义标签比如姓名拼音年份、小写单词手机尾号、键盘路径特殊符号等等。模式分析引擎还有一个统计输出功能。处理完所有样本后它会产出一份模式分布报告告诉用户当前样本集中最常见的TOP20模式分别占多大比例平均密码长度是多少字符类别组合的分布情况如何。这份报告同时会写入日志文件方便后续复盘对比。2.2 基于统计概率的规则引擎规则引擎是整个生成器的动力系统它负责把模式分析得到的特征变成一条条可执行的生成规则。我设计这套规则时参考了Hashcat和John the Ripper的规则语法但又针对概率排序这个核心目标做了很多改动。规则的表达形式是一个模板加一组参数。模板由字面字符和占位符组成占位符用花括号加语义类型表示。比如{name_pinyin}{year_4d}{special_1}就代表姓名拼音加4位年份加1位特殊符号这个结构。这组模板来自三个渠道一是模式分析引擎对样本聚类后自动归纳出的高频结构二是预置在工具内部的常见密码模式库三是用户通过配置文件自定义的手写规则。概率排序是怎么实现的我给每一条生成规则都关联了一个初始权重值。这个权重值在样本分析模式下直接来源于该模式在样本中的实际占比在无样本模式下则来源于内置统计库中该模式的先验概率。当一个密码生成出来后它的最终排序分由三个因素共同决定生成它的那条规则权重、它命中的每个词条本身的词频、以及一些惩罚因子。惩罚因子用来压制那些明显不人类的高熵组合比如连续超过4位的无意义大小写混排或者随机字符长串。这里还要特别处理规则的组合爆炸问题。比如规则模板有100条基础词表有5000个词年份库有40个值如果无条件全组合轻松就能生成上千万条数据。所以我在规则引擎里加了三个限制参数单条规则的最大产出上限、全局总产出上限、按权重截断的阈值。默认配置下每条规则的产出上限是50000条全局上限是200万条权重低于整体峰值百分之一的规则直接被跳过。2.3 多层次词表体系词表是密码生成器的弹药库。这个工具的词表体系分为四层每层承担不同的任务。第一层是全局通用词表包含各类泄露数据中统计出的最高频密码词组大约12000条覆盖123456、password这一类的顶级弱密码。第二层是中文语境专有词表包含高频姓名拼音、常见网名格式化变体、常用手机号前缀组合、地理名称等这一层的价值在于能适配国内目标的大多数场景。第三层是场景定制词表由使用者在每次测试前通过配置文件注入。比如测试对象是一个叫星云的内部系统你就可以把xingyun、xinyun、nebula、xingYun这类词根放进去工具会自动扩展大小写变体和各种组合。第四层是动态衍生词表它基于前面三层的词条自动生成变体包括尾部追加连续数字、键盘平移一位、部分字母转相似数字等。词表的权重管理是这套体系里最繁重的活。每个词条基础权重来源于它在真实泄露数据中的出现频率但不同来源的词表统计口径不一样直接合并会导致权重失真。我的处理方案是引入一个加权归一化过程给每一层词表设定一个基准权重倍数再用一个全局的对数平滑公式把最终权重压到一个可比区间。同一层内如果检测到明显重复的词条则保留最高权重值去重。2.4 概率排序与输出管理生成完所有候选密码之后真正的重头戏才刚开始。这个模块要做的是把所有候选数据按命中概率从高到低排成一个流式序列同时按照用户设置的规模上限截断输出。排序算法采用的是多级归并排序加堆顶截断。每个生成规则独立产出一个有序片段片段内部按照词频权重和变体权重排序。这些片段输入到一个大小受限的最小堆中堆容量等于最终需要输出的条目数。这样无论中间产生了多少候选条目内存占用始终可控不会出现生成到一半机器就卡死的情况。输出格式上支持纯字典文件、带权重的TSV文件、以及HMAC格式的哈希值列表。带权重的TSV文件特别有用因为你可以把它喂给后续的在线爆破工具让工具按权重字段决定尝试顺序。HMAC格式则是为了在敏感环境下测试避免字典文件本身成为泄露风险。规模控制是这个模块的重要卖点。默认配置下一个带10万条种子词库、200条规则的生成任务最终输出规模会被压缩到80万条左右文件大小约60MB。相比传统工具动辄数GB的产出这个体量让后续测试工具可以轻松加载甚至在网络带宽受限的环境下也能直接传输。3. 关键算法与实现细节3.1 模式提取的核心逻辑模式提取的第一步是对原始密码做标准化预处理。因为真实世界里的密码样本数据质量参差不齐有的混入了空格、有的混入了URL编码残留、有的是全角字符和半角字符混排。预处理的规则包括统一转为半角字符、去除首尾空白、过滤明显不是密码的过短或过长条目。完成清洗后每条样本保留一个至少4位、至多32位的有效密码串。接下来执行字符类别标记。这一步把每个字符映射成类别标识然后把相邻相同类别压缩成一个段。比如Zhang2024!这个密码经过标记和压缩后会变成[拼音段][数字段][符号段]的结构。这里的关键是中文拼音词的识别。我预置了一个覆盖约6000个常见姓和名的拼音库匹配时优先尝试最长后缀匹配避免把wang拆成wan和g两个无意义片段。数字段的语义识别同样重要它会尝试把每一段数字解析为年份、日期、电话号段、递增序列或简单重复序列。识别优先级是先判断年份模式因为19xx和20xx开头的四位数年份在中文用户密码里出现频率极高。不是年份的话再依次判断是否是月日组合、手机号尾数规则、键盘横向路径、连续递增或递减。完成模式提取后所有样本都变成了一组结构化的模式实例。下一步是模式聚类我把结构完全相同、且对应词库词条也完全同类的样本归为一类统计每一类的出现频次和占比。聚类的输出结果直接喂给规则引擎其中占比超过阈值1%的模式会被自动注册为高权重规则。3.2 规则语法的设计与示例规则语法设计得太复杂用户不愿意看太简单又表达不了精细的控制意图。我调研了现有工具的语法之后决定采用一种折中方案以明确的语义占位符为主配合少量修饰参数。完整语法文档可以看项目内docs目录这里列几个最常用的示例。# 基础结构姓名拼音加4位年份 {name_pinyin}{year_4d} # 常见结构英文单词加2位数字加1位特殊符号 {word_lower}{digit_2d}{special_1} # 键盘路径单独生成这属于高频弱密码模式 {keyboard_path} # 指定词根加年份再加可选的符号序列 word_root:{root1,root2,root3} {root}{year_2d}{special_optional}符号开头的行是自定义词根定义。后面的大括号引用这些词根生成组合。_optional后缀用于声明该段可以缺省_2d和_4d后缀用于限制数字段的长度_1表示只取一个字符。规则文件支持用#注释也支持用weight数字给特定规则指定权重覆盖默认值。规则引擎执行组合时有一个细节需要注意就是占位符的数量级控制。一条规则里的自由变量合计可能高达百万级别所以每条规则内部也遵循词频优先的生成顺序。比如{name_pinyin}这个段会先选高频姓氏再选高频名字最后才尝试低频生僻组合。这样即使规则本身产出上限是50000条前面几千条也大概率覆盖了最可能命中的尝试。3.3 概率权重计算公式概率排序是整个工具最有技术含量的部分。排序分数的计算公式我迭代了很多版本最终使用的简化模型如下score 0.45 * rule_weight 0.30 * word_weight_avg 0.15 * length_penalty 0.10 * pattern_penaltyrule_weight是生成这条密码所用规则的归一化权重取值范围0到1。word_weight_avg是密码中所有命中词条的平均词频权重同样归一化到0到1区间。length_penalty是长度修正函数经验上8到11位是最常见的人类密码长度区间这个区间长度为满分1.0低于6位或高于20位都会逐步衰减。pattern_penalty是惩罚项专门压低那些过于随机或过于机械的生成结果。长度惩罚函数的具体曲线我是靠实测数据拟合的。早期版本用的是线性衰减结果排序效果不理想纯数字的短密码经常排名过高而13位以上的组合型密码长期排不进头部。后来改成二次函数衰减加区间峰值模型排序质量才明显改善。最终实现是这样一段逻辑def length_penalty(length): if 8 length 11: return 1.0 if length 8: return max(0.4, 1.0 - (8 - length) * 0.12) return max(0.3, 1.0 - (length - 11) * 0.06)模式惩罚的设定主要是为了拦截三类低价值结果超过4位的纯数字串被压到0.7倍权重因为纯数字暴力破解本来就不需要字典包含连续3位以上键盘横向序列的密码被压到0.8倍包含超过6位随机大小写混排、没有命中任何语义库的密码被压到0.55倍。这些惩罚因子单独看都不大但叠加起来就能显著改变排序头部的内容构成。3.4 多线程生成与内存优化生成任务的规模虽然被控制了但原始组合过程仍然可能触及千万级的数据量。为了不让生成阶段成为性能瓶颈我在工具里实现了多线程生成框架。默认启用CPU核心数减一个线程每个线程独立负责一组规则的展开把生成的条目写入本地临时分片文件。所有分片文件都经过独立的快速排序后再进行多路归并。多线程生成带来的最大问题是重复条目。不同规则可能产出相同的密码所以归并阶段必须做去重。去重采用了两段式方案第一段用布隆过滤器做粗筛快速丢弃确定重复的条目第二段对通过粗筛的关键候选重新做哈希校验。首先生成的候选按概率从高到低写入最终文件这样天然保留了概率排序的顺序。内存优化方面有一个设计原则就是不把完整候选集驻留在内存里。每个线程维护一个固定大小的缓冲队列队列满就刷盘。刷盘后的临时文件全部放在临时目录里任务结束后自动清理。实测在16GB内存的机器上生成200万条候选数据峰值内存占用不超过800MBCPU占用维持在八九十个百分点。4. 实操过程与参数验证4.1 实战一有密码样本的定向审计这个实战场景来自一次授权的企业内部密码安全审计。客户提供了1200条测试回收的明文密码样本我直接把这些样本作为模式分析引擎的输入目标是为后续的暴力破解生成一个有针对性的字典。任务背景是这样的目标系统限制了每个IP每小时只能尝试30次登录也就是说我有一整天的测试窗口最多也只能试720次。这个次数限制迫使字典必须极其精简前几百条就要覆盖最大的可能性。运行模式分析后拿到了几个关键数据。样本中最常见的模式是姓名拼音全拼4位年份占比21%姓名拼音首字母手机号尾号4位占比13%纯手机号占比11%英文常用词2位数字占比9%。剩余模式都比较分散各有三到五个百分点。这些数据直接决定了后续规则文件的配置方向。我按分析结果配置了规则文件把高占比模式的规则权重设为默认值的1.5倍并把四条高占比模式的产出上限设为10000条。接着指定了一批目标系统相关词根比如公司英文名简写、部门名称、常用系统名字。生成结果规模控制在18万条在720次尝试限制下我按排序顺序截取了前700条去测试最终命中了31次准命中率4.4%。这个成绩在单账号在线测试场景下相当可观了。4.2 实战二完全无先验信息的通用测试第二个场景是没有样本可用只知道目标是一个对外业务系统使用人群是普通互联网用户。我跑了无监督模式工具自动加载内置统计库和基础词表生成一份通用字典。内置统计库的内容来自我之前多年积累的调研数据包含了高频密码模式分布、常用词频、键盘路径序列库等信息。这个模式下的规则生成不需要做样品分析直接按照先验概率展开。工具默认配置给出的通用字典约200万条按排序截取前80万条用于测试。实测下来纯无先验字典的命中率当然比有样本审计低很多整体只在0.3%到0.5%这个区间。但关键是排序靠前部分表现亮眼前1万条就贡献了全部命中量的大约四分之一。这说明了概率排序的价值所在即使整体命中率不高前面的尝试也远比你随机试快得多。这个测试也验证了一个经验用通用字典跑的时候不要贪多。200万条里真正有用的往往就是前面三五十万条后面那些低概率尾巴对命中率的贡献微乎其微还会拖慢工具加载和测试进度。我后来默认把无样本模式的输出上限调低到120万条。4.3 规则参数调优的迭代过程参数调优是这个工具打磨过程中最耗时的一部分。早期版本的默认参数比较保守比如通用词表只取了8000条年份库只覆盖1980到2005年导致生成结果里面缺少了大量90后、00后的年龄段特征密码。后来通过分析多轮测试的命中日志我逐步扩大了几个关键参数。年份库扩展到1970到2024年覆盖更广的年龄层全拼姓名的权重做了加权因为中文场景里全拼的比例明显高于首字母缩写。手机号段生成规则增加了常见运营商前缀的权重让高频号段排在前面低频号段沉底。调优过程里还有一个值得记录的点特殊符号的使用。早期规则里给特殊符号分配了比较高的权重认为8位密码加特殊符号很常见。但真实测试数据表明国内普通用户密码里带特殊符号的比例远低于想象。后来我把特殊符号段的权重下调了30%同时把符号类型集中在!#$%()*等高频位排序效果好了不少。4.4 性能实测数据在正式发布前我在三台不同配置的机器上做了性能基准测试。测试配置分别是一台4核8GB内存的旧笔记本、一台8核16GB的中端台式机、一台16核32GB的工作站。任务统一为有样本模式输入1200条样本产出目标80万条候选数据。测试结果如下表所示硬件配置模式分析耗时规则生成耗时排序去重耗时峰值内存4核/8GB3.2秒41秒35秒620MB8核/16GB1.8秒19秒16秒790MB16核/32GB1.1秒9秒7秒810MB可以看到内存消耗并不随CPU核心数增加而线性上升因为刷盘策略起到了作用。耗时则明显受CPU核心数影响多线程扩展效率接近线性。这个性能表现面对日常测试任务基本是秒级到分钟级的水平不需要为生成字典单独准备高性能服务器。5. 高频问题与排查经验5.1 生成结果里大量重复和相似条目早期版本遇到过比较严重的重复问题。两个高频规则可能产生相同结果比如姓名拼音全拼年份和姓名拼音年份虽然规则模板不同但实际生成的密码串完全相同。后来在生成管线里增加了一道规范化处理生成每个条目时先做一次快速哈希并写入归并缓冲只有确认新条目才会进入排序队列。相似条目的问题则更隐蔽。一些变体规则只是在原词基础上加了一个相同前缀或后缀导致最终文件里大量条目高度相似占用了宝贵的前排位置。解决思路是增加一个去相似度的选项默认关闭需要时打开。开启之后系统会按最长公共子序列做一个局部聚类把相似度超过阈值90%的条目只保留一组。实际操作经验是在线测试场景建议开启去相似度开关因为有限次尝试内相似条目的收益递减明显。离线破解GPU场景则可以关闭因为GPU不在乎重复条目反而需要覆盖更多变体。5.2 内存溢出和临时文件堆积生成大规模任务时如果规则文件写得过宽比如单个规则里的基础词表有20万条、变体规则又开了全量组合中间数据量会瞬间飙升。即便有刷盘机制临时文件也可能短时间内堆积到几个GB甚至占满磁盘。排查这类问题的第一步不是优化代码而是检查规则文件定义。规则模板里自由变量的总数超过50万条时工具会打印警告日志并建议用户使用max_rule_entries参数调低单条规则产出上限。真正需要重启任务的时候临时目录下残留的分片文件可以安全手动删除工具不会依赖上次运行的状态。内存溢出的一个典型预警信号是生成阶段日志里出现大量flushing buffer提示。出现这个说明缓冲队列频繁刷盘磁盘IO成为瓶颈。调优方向是提高buffer_size参数并检查临时目录的读写速度如果临时目录和输出目录在同一个慢速机械硬盘上换成SSD会立竿见影。5.3 概率排序结果不符合预期有用户反馈排序靠前的密码里出现了一些明显不合理的组合比如长串随机字符排在了简单生日密码前面。排查后发现大多是指定了自定义词表导致的。自定义词表的词条权重默认值偏乐观工具会以为你手动指定的词都是高概率词给了很高的权重。实际上手动词条需要显式指定weight参数才能获得相应的优先级否则会按照默认的低权重处理。另一个常见原因是规则文件里手工指定的自定义权重没有经过归一化。如果某条规则的权重值设置为100而其他规则默认都在0到1区间排序分就会被这一条带偏。工具在新版里加了权重范围检查发现超过合理区间的值会在加载时自动修正并打印警告这样基本不会再出现整体排序被单条规则带偏的情况。5.4 生成结果中缺失预期模式这个问题的典型场景是明明在规则文件里配置了某类组合生成的字典里却没有或极少出现。首先要检查的是该规则是否被全局产出上限截断了。全局优先级是规则权重高的先生成低权重规则如果排得太靠后很可能轮不到展成就被上限截断。这种情况调整本规则的权重值或者调高全局产出上限就能解决。其次要检查词表库。很多模式下工具的可用词条本身就有限。比如地名拼音年份这种结构依赖地名拼音库的覆盖度默认词表只收录了常见省会城市如果你需要区县级地名就必须在自定义词根里手动补充。工具不会自动去爬取外部词库所有词条来源都需要用户在配置里预先准备好。6. 合规边界与个人实践总结密码生成工具是把双刃剑这一点必须当面说清楚。PrecisionPasswordGenerator的定位是授权安全测试、企业自检、个人密码强度评估绝不是用来做未授权攻击的。涉及真实系统测试时必须确认拿到书面授权测试过程严格限定在授权范围内生成的数据用完即删不保留不传播。我在实际使用中还有几条个人经验想特别分享。一是无论工具的概率排序做得多好线上测试永远比离线破解更依赖排序质量因为线上尝试次数受限必须把好钢用在刀刃上。二是做模式分析时样本量不要太小低于三五百条的样本统计波动很大可能给你一个偏离实际的模式分布。三是不要把生成的字典文件直接放在明文共享盘里用HMAC格式或者加密压缩文件保存这个行业里因为字典文件泄露翻车的案例并不少见。最后再分享一个具体的小技巧。每次完成一次测试任务后我会把实际命中结果和它们对应的生成规则记录到一个反馈文件里。积累几轮之后把这些反馈重新灌入工具的训练集调整对应模式的权重和词表。这本质上是一个自我演进的过程工具用的时间越久、积累的反馈越多后续生成字典的命中率就会越高。这个循环迭代的思路才是PrecisionPasswordGenerator真正的价值所在它不是一个静态的生成器而是一个越用越懂目标体系的密码猜测系统。