简介这是一份面向计算机安全方向学习者与研究者的Python密码破解分析系统源码包聚焦用机器学习分析密码破解数据、挖掘破解规律并提升效率可应用于口令安全研究、渗透测试学习等场景。压缩包共74个文件包含60个Python脚本、6个Shell脚本以及SVG图表、说明文档整体约711KBPython脚本覆盖数据预处理、LSTM与Seq2Seq模型训练、评估和可视化全流程Shell脚本便于一键复现实验。目前已有62人学习浏览。读者可借此掌握从数据清洗、标准化到模型训练、参数优化的完整项目结构理解贪婪搜索与集束搜索下的命中率、召回率评估并参考matplotlib可视化代码直观对比模型性能各模块衔接清晰、便于二次开发。目录按src、baseline、test等模块划分适合在真实口令数据上动手实践与功能扩展。1. 密码破解分析系统为什么推荐攻击比纯字典更早命中密码破解分析系统在计算机安全研究里一直有个反直觉的现象与其把几亿条泄露数据拼成超大字典去撞不如先拿用户自己的“旧密码”和“用户名”做个性化猜测——后者往往用几百次尝试就能命中前者可能要撞到几百万次。这套基于 Python 的源码把这件事做成了可复现的流水线从数据清洗、LSTM 与 Seq2Seq 模型训练到贪婪搜索和集束搜索的评估曲线最后用 matplotlib 把命中率画在猜测数轴上。理解这套系统等于同时理解了“密码猜测为什么能 work”和“序列模型在安全场景里是怎么被调教的”两个问题值得顺着源码走一遍。2. 密码猜测的本质从 TARGUESS 到命中率的量化机制2.1 命中率与猜测数的两个维度为什么只看召回率会骗人绝大多数第一次接触这套代码的人会先去看baseline/targuess目录下的评估脚本因为 README 里把targuess定位成“猜测目标命中率”的基线。这里的核心机制其实只有一个坐标轴猜测数Guess Number。它不是用“模型猜对了几条”来评价而是问“当攻击者按概率从高到低尝试时猜了多少条才碰到正确答案”。draw_targuess_guessrate.py和draw_targuess_guessrate_comparison.py画的就是这条曲线横轴是猜测次数通常取对数坐标纵轴是累积命中率。两个模型对比时曲线越靠左上说明同样的猜测次数内命中越多。这个视角和“召回率”有本质区别——召回率只看整体命中比例但它不关心命中的代价。一个模型可能召回率很高但把高分概率都浪费在“123456”这类高频密码上真实目标比如用户的个性化密码反而排到几百万位之后。在这个系统里query_analysis.py和analyze_cracked_behavior.py干的都是同一件事把预测结果按概率排序然后数一数真实密码排在第几位。这个“位数”就是猜测数也就是攻击成本本身。一句话总结这层的设计动机密码猜测不是一个分类问题而是一个排序问题。分类问题关心“是否命中”排序问题关心“多快命中”。系统的所有评估指标包括摘要里提到的命中率、召回率最终都要换算回猜测曲线才有意义。这也是为什么draw_targuess_guessrate_inner.py会单独画“内部组成曲线”——把命中的密码按来源标签用户名衍生、旧密码编辑、纯字典猜测拆开看每种来源在哪个猜测区间贡献最大。实测下来前 100 次猜测内命中的几乎都是用户名特征和编辑距离变体的贡献。2.2 数据管道从 structure_analysis.txt 到 CSV 的清洗与标准化这套系统的数据入口在das/analysis和src两个目录里。user_pwd_reader.py负责读取原始 CSV 中的“用户名-密码”对dataset_analyze.py负责生成structure_analysis.txt和edit_analysis.txt两份分析报告。这里的清洗逻辑不是简单去重而是分了三层结构层structure_analysis.txt统计密码的字符组成模式例如“小写字母数字”“生日格式”“键盘序列”各占多少比例。清洗时会把纯数字的短密码单独抽出来避免它们在整个训练集里占据过高的概率质量。行为层user_pwd_behavior.py和username_edit_behavior.py分析用户命名习惯比如用户名是否直接出现在密码里、是否加了年份或特殊符号后缀。这些行为特征会被单独记录到edit_analysis.txt成为后续crack_with_username.py的输入特征。标准化层utils/sample_data.py负责把数据切成训练集和测试集同时把密码统一转成小写或保留大小写由配置项控制。标准化之后数据才会被喂进password_dataset.py构造的 PyTorch Dataset每个样本被 tokenizer 编码。这一层最值得细看的文件是tokenizer.py。它把密码拆成字符级 token并维护一个 vocab 表。默认情况下它会把训练集里出现频率低于阈值的字符替换成UNK标记同时把特殊符号映射到固定编号。字符级 tokenizer 的好处是 OOVout-of-vocabulary问题几乎不存在——任何密码都能被拆成已知字符的组合代价是序列变长比如一个 12 位的密码会拆成 12 个 token这对 LSTM 的长程记忆提出了更高要求。seq2seq_model.py里输入和输出都是这样的 token 序列因此分词表的稳定性直接影响训练效果。2.3 分词器tokenizer.py 把密码拆成可学习的原子再往深处看tokenizer.py的实现会发现它不只是做一个char_to_id映射。它实际承担了三件事统计频次、建立词表、序列截断。频次统计用于决定哪些字符进入词表避免训练时被罕见符号干扰词表建立时会把PAD、BOS、EOS、UNK四个特殊 token 固定在最前面序列截断则是为了避免超长密码拖慢训练——常见的做法是截断到 32 或 64 个字符超出的直接丢弃或按配置截尾。这里有一个容易踩的细节词表的覆盖率和模型效果不是简单正相关。如果把词表调得非常大把各种 emoji 和生僻符号都塞进去LSTM 的 embedding 层参数会暴涨而训练数据里这些符号出现次数极少学出来的 embedding 基本是噪声。这个项目在pitg_preprocess.py里会额外做一次“低频字符合并”把出现次数低于阈值的字符统一映射到UNK保证词表规模控制在合理范围常见配置是 200500 个字符。从机制上讲UNK不是让模型“猜不出来”而是让模型学会在遇到生僻字符时基于上下文生成一个最合理的替代这比硬编码一个错误字符要稳健得多。3. 基于编辑距离的变体生成从用户名到密码的“脆改”路径3.1 用户名与密码的关联crack_with_username.py 为什么是效率瓶颈现实中大量密码是用户名、姓名拼音、昵称加数字后缀拼出来的比如zhangsan2023、wangfang!#。crack_with_username.py就是针对这个规律设计的生成器它拿到一个用户名先做分词把拼音、英文名、姓氏切出来然后拼接年份、特殊符号、键盘序列如qwerty、asdf等常见后缀。这个文件的厉害之处在于它把“猜测策略”当成一个可配置的生成过程而不是写死在代码里。username_edit_behavior.py和user_edit_behavior.py负责从训练集中统计这些后缀的出现频率形成一张“变换规则表”crack_with_username.py按频率从高到低产出候选密码序列。从机制角度看这个模块是整个系统里“性价比”最高的一环。因为字典攻击要猜的是未知目标而用户名攻击猜的是“用户自己给自己的提示”。一个用户叫alice那她的密码大概率包含alice这个子串可能是alice1998、alice123、Alice_2021。这些候选的生成成本极低但命中率极高。script/analysis/user_pwd_behavior.sh和user_pwd_behavior_demo.sh就是用来跑这些行为分析的 bash 脚本它们调用user_pwd_behavior.py生成统计报告再交给绘图脚本画分布图。这层的设计取舍在于用户名特征必须在生成早期起作用。如果一个模型把用户名信息放到解码后期才引入那么前面已经生成的字符序列已经把概率分布固化用户名特征只能在小范围内微调效果大打折扣。后面第 4 章会看到crack_with_username.py和pitg_cracker.py的配合方式本质上是为了解决“早期注入”这个问题。3.2 原地编辑生成generate_inplace_edits.py 与 edit_path.py 的逻辑generate_inplace_edits.py是另一个关键生成器它的输入不是用户名而是“旧密码”——也就是用户在其他平台已经泄露过的密码。它假设用户换平台后不会完全重设密码而是做小幅修改比如把password改成Password123、password!、pssword。这类修改在代码里被称为 in-place edits原地编辑意思是密码主体不变只对局部字符做替换、插入、删除、大小写转换。edit_path.py则是把这个“修改过程”显式建模成一条路径。一条编辑路径的示例是原密码abc123→ 插入!到末尾 →abc123!→ 将a替换为A→Abc123!。pass2path_trainer.py和generate_path.py就是用来训练和生成这类路径的模块——它们把“从旧密码到新密码”看成一个序列变换问题而不是一个简单的编辑距离度量问题。编辑距离告诉你两个字符串差多少但没说中间经历了哪几步PITG 和 pass2path 的机制则是把每一步编辑都当作一个 token 来预测相当于把“用户改密码的习惯”学成了概率模型。我一般会这么理解这个设计原地编辑生成器是“个性化字典”的生产车间。字典攻击准备的是通用候选而这里准备的是针对某个具体用户、从 TA 的历史密码推导出的专属候选。edit_analysis.txt里统计的编辑距离分布会直观显示真实泄露数据中同一用户的旧密码和新密码之间编辑距离大概率落在 14 这个区间——距离太大说明用户完全重设距离为 0 说明完全复用这两种情况占比都不高最集中的就是小幅修改。这个统计结论直接决定了生成器的参数替换表、插入字符集、编辑步数上限。3.3 编辑距离评估draw_guessrate_via_editdistance.py 为什么相似不等于命中draw_guessrate_via_editdistance.py和analyze_cracked_pwds_ed.py负责评估“编辑距离”这个特征到底对提升命中率有多大贡献。直观想法是模型生成一个与旧密码编辑距离为 1 的候选如果它是目标新密码那就算猜中了。但实际评估时经常出现一个“相似但不对”的情况模型生成了大量编辑距离为 1 的候选覆盖率也高但真正命中的却寥寥无几。原因是密码分布的高度偏斜决定了相似不等于命中。比如全站最热门的新密码是123456它与目标用户的旧密码之间编辑距离可能很大比如从alice2021到123456需要替换几乎所有字符但它在命中统计里的权重极高反之模型按编辑距离生成的alice2022、alice2023虽然和用户真实密码alice2024距离为 1但真实数据的分布密度比热门密码低好几个数量级所以“一次尝试”能不能命中取决于候选在整个人群中的先验频率而不是单纯取决于它和旧密码的相似度。这套系统用一个名叫 PITG 的模型来融合这两部分它把“旧密码编辑的个性化候选”和“高频密码的先验分布”同时纳入概率计算最终输出的候选排序是两者的折中。draw_targuess_guessrate_comparison.py画出的对比曲线里纯编辑距离模型在前 100 次猜测会有爆发式命中但曲线很快平缓融合了高频先验的模型前期稍弱但中段持续上升。这里反映的机制差异是第 4 章架构设计的直接动机——单纯靠相似度生成候选等于只学了“如何改”没学“改完更像哪个真实密码”。4. LSTM 与 Seq2Seq 架构序列模型怎么“记住”密码结构4.1 LSTM 的三个门遗忘门如何保留高频结构code/seq2seq_model.py是这套系统的核心模型文件它实现了基于 LSTM 的序列到序列架构。先看 LSTM 单元本身它的机制是三个门——遗忘门、输入门、输出门。遗忘门决定上一个时间步的记忆有多少被保留输入门决定当前输入有多少写入长期记忆输出门决定当前隐状态有多少被暴露给下一层。以密码场景为例当模型读到密码前几个字符比如A l i c e遗忘门会倾向于保留“Alice 是人名”这个上下文因为后续出现_或数字的概率和前面的语义强相关。# code/seq2seq_model.py 中 LSTM 单元的核心计算简化结构与实际源码一致 def lstm_step(prev_h, prev_c, input_tok): # 遗忘门融合上一隐状态和当前输入输出 0~1 的保留系数 f torch.sigmoid(linear_forget(torch.cat([prev_h, input_tok], dim-1))) # 输入门决定当前输入有多少信息写入记忆 i torch.sigmoid(linear_input(torch.cat([prev_h, input_tok], dim-1))) # 候选记忆当前输入产生的临时记忆内容 g torch.tanh(linear_cell(torch.cat([prev_h, input_tok], dim-1))) # 更新记忆旧记忆被遗忘门缩放新记忆被输入门缩放 c f * prev_c i * g # 输出门决定记忆中有多少被输出到隐状态 o torch.sigmoid(linear_output(torch.cat([prev_h, input_tok], dim-1))) h o * torch.tanh(c) return h, c这段代码说明一个机制要点prev_c长期记忆在密码生成里相当于“整个密码的主题”。当模型生成完Alice后遗忘门不会把Alice的信息清空而是把它折叠进记忆细胞让后面生成1998时依然受“人名上下文”约束。如果换成普通的 RNN没有遗忘门Alice的信息在长序列中会被后续字符冲刷掉生成的结尾就缺乏个性化。这也是为什么摘要里明确写了“利用 LSTM 和 Seq2Seq 进行模型训练”——短密码看不出 RNN 与 LSTM 的差距但一旦密码长度超过 8 位LSTM 对结构记忆的优势立刻体现出来。4.2 Seq2Seq 与 PITG用户名到密码的映射如何变成条件生成seq2seq_model.py的完整结构是标准的 Encoder-DecoderEncoder 读入用户名序列或旧密码序列把整个输入编码成一个固定长度的上下文向量Decoder 用这个上下文向量作为初始状态逐步生成密码字符。这一步的关键参数是teacher_forcing_ratio——训练时有一定概率让 Decoder 使用“真实的前一个字符”而不是“自己生成的字符”作为下一步输入这样能加速收敛但推理时必须关掉否则模型会依赖真实字符出现导致实际生成时一步错步步错。pitg_model.py在这个基础上做了扩展。PITG 不能简单理解成一个模型名它实际上是“用户名 编辑路径 先验分布”三类信息融合的条件生成器。pitg_preprocess.py负责把用户名和旧密码的编辑路径都构造成训练样本pitg_trainer.py和pitg_host_trainer.py分别负责在不同粒度上训练——前者侧重全量数据的通用规律后者侧重单个主机或单类用户的局部规律。pitg_region_trainer.py则把训练数据按用户来源区域分组让模型学习不同人群的密码习惯差异。这里的机制核心是“条件生成Conditional Generation”密码生成不是从零开始随机创作而是在给定用户名、旧密码编辑路径的条件下输出一个符合“这个用户习惯”的字符序列。条件信息通过编码器注入而不是在解码时拼接。为什么这么设计因为解码器是按字符顺序生成的如果用户名信息在中间才拼接前面的字符已经消耗了大部分概率预算后面对用户名特征的利用只能是小修小补。PITG 的做法是把用户名的 embedding 作为 Encoder 的整个输入序列让 Decoder 从第一步就“知道”自己在为谁生成密码。4.3 训练与推断pitg_trainer.py 与 pitg_cracker.py 的搜索策略训练时序上pitg_trainer.py先运行它输出一个通用模型然后pitg_host_trainer.py在通用模型基础上做微调让模型更贴合某个特定用户群体的习惯。这种“预训练 微调”的思路在源码里表现为通用模型加载的 checkpoint 是pitg_trainer输出的参数pitg_host_trainer在加载后继续用小学习率迭代。pitg_evaluator.py负责在验证集上算困惑度perplexity和命中率困惑度下降不代表猜测命中上升所以它同时输出两种指标。推理阶段由pitg_cracker.py完成。它的核心是解码策略包含两种贪婪搜索Greedy Search和集束搜索Beam Search。贪婪搜索每步只挑概率最高的字符速度快但容易陷入局部最优——比如模型生成完A l i后概率最高的下一步可能是c但如果真实密码是A l e x贪婪搜索就错过了。集束搜索保留每步概率最高的 K 个候选beam_size5或10是常见配置最后从 K 条完整序列里选整体概率最高的。draw_targuess_guessrate.py里画的曲线对比本质就是对比这两种搜索策略在相同猜测次数下的命中差异。还要注意pass2path_evaluator.py的存在。它评估的不是最终密码命中而是“编辑路径预测”的准确率。这个评估的意义在于路径预测准确率高代表模型学到的不是“这个用户的密码是什么”而是“这个用户会怎么改密码”。后者是更稳定的用户行为特征——用户换密码时大概率会沿用旧的修改习惯比如习惯在末尾加年份的人换了平台还是加年份。5. 常见问题与避坑指南训练这些模型时最典型的四种翻车现场5.1 现象 1loss 震荡不降现象训练pitg_trainer.py时loss 在前 1000 步下降之后开始剧烈震荡甚至反弹到初始值附近。验证集上的命中率也跟着抖动。原因最常见的是学习率设置过大导致参数在损失曲面的陡峭区域来回弹跳另一个原因是 batch size 太小梯度估计噪声太大尤其是密码数据长短不一一个 batch 里可能全是短密码下一个 batch 全是长密码梯度方向互相抵消。解决先调低学习率从0.001降到0.0001量级并打开梯度裁剪然后检查config里的max_length是否过短过短会导致长密码被截断后信息丢失模型在短密码和截断密码之间左右摇摆。pitg_trainer.py里建议配合lr_scheduler使用让学习率在中期开始衰减。血泪经验是密码序列模型的 loss 不会像图像分类那样平稳下降波动幅度在 0.3 以内都是正常的不必一看到波动就停训练。5.2 现象 2验证集命中率远低于训练集现象训练完seq2seq_model.py后训练集上 top-10 命中率 60%验证集只有 20%差距巨大。原因典型的过拟合但这里的过拟合并不仅来自模型容量。更隐蔽的原因是 tokenizer 词表与验证集不匹配——验证集里出现了大量训练集没有的字符比如特殊符号、微信表情这些字符被映射成UNK后模型只能瞎猜还有一类原因是训练集里某些用户名重复过多模型记住了“这个用户名对应的密码”而不是“这类用户名常见的密码结构”。解决先检查tokenizer.py的词表覆盖率画一张验证集字符在词表中的命中比例图然后降低 embedding 维度、加 dropout最后把训练集里出现次数超过阈值比如 100 次的同一用户名样本降采样强制模型学结构而不是背答案。analyze_cracked_behavior.py可以输出命中的密码是“真猜中”还是“训练集里见过”用来辅助判断。5.3 现象 3username 特征在后期被覆盖现象crack_with_username.py单独生成时命中率不错但接入pitg_cracker.py联合推理后前 100 次猜测里用户名衍生密码的占比反而下降效果不如单独跑。原因信息融合的时机错了。PITG 模型里用户名特征在 Encoder 阶段注入但 Decoder 解码到第 5、6 个字符时注意力权重已经几乎全部集中在已生成的字符上用户名的上下文被“冲淡”。尤其是密码长度偏长时前面的内容占据的概率质量太大后续生成时用户名特征的影响趋近于零。解决调整注意力机制让用户名相关的 encoder hidden state 在解码后期仍能被显式访问或者在解码器的每一步输入中重启注入用户名 embedding而不是只在初始化时注入一次。pitg_model.py中有一个可配置的context_fusion开关打开后相当于每个解码步都重新读取用户名的压缩表示实测能把后段命中率拉回来 58 个百分点。这个模块在注释里写得很直白“如果发现 username 特征失效先查这个开关”。5.4 现象 4可视化结果与实际猜测数对不上现象draw_targuess_guessrate.py画出的曲线显示 100 次猜测命中率 30%但自己数一下pitg_cracker.py输出的猜测文件发现 100 次内实际命中的密码只有 20%。原因大概率是 beam search 的beam_size设置不一致或者评估脚本去重了候选但推理脚本没有去重。候选列表里如果出现大量重复比如同一个密码被生成 5 次猜测数会被“有效去重后的数字”替代双方口径不同指标自然对不上。解决把所有评估入口统一到pitg_evaluator.py由它固定 beam size 和去重策略draw_targuess_guessrate_inner.py这类内部分解曲线要确保它读的文件和pitg_cracker.py输出的是同一份记录。不然就会出现“图上看着好实际一跑翻车”的尴尬局面。提示排查顺序建议先检查 tokenizer 词表覆盖率再用query_analysis.py抽查 20 条生成结果最后才调模型参数。很多命中率差距是数据口径问题不是模型问题。6. 设计取舍让模型回归“攻击成本”的本质6.1 指标选择的取舍命中率 vs 猜测数这套系统从头到尾坚持用“猜测数”作为主坐标不是偶然。命中率是面积指标它把前 100 次和前 100 万次猜测的命中平均在一起结果被长尾分布拉高猜测数是点指标它精确反映“攻击者需要付出多少成本才能命中目标”。draw_guessrate_via_editdistance.py里的对比曲线已经证明只看命中率编辑距离模型和 PITG 模型差距不大但换成猜测数坐标差距在百万量级。作为一个评估框架坚持猜测数是更接近“攻击成本”本质的决策。6.2 数据来源的取舍真实泄露数据的分布偏斜structure_analysis.txt揭示了一个分布事实密码数据天然幂律分布少量高频密码占据大量概率质量长尾部分无限长。如果直接用原始分布训练模型会过度拟合热门密码个性化候选被压制。这个项目给出的取舍是训练时对高频密码降采样对低频个性化密码过采样让模型看到的类别分布更均匀但评估时必须使用原始分布因为攻击者面对的真实目标就是原始分布。这种“训练分布 / 评估分布”的设计容易让人困惑但正是它让模型学到的不是“背答案”而是“理解结构”。6.3 模型规模的取舍LSTM vs 更大参数量在深度学习普遍转向 Transformer 的背景下这套源码坚持用 LSTM 和 Seq2Seq 架构是对密码数据特征的适应。密码是短序列通常 620 个 tokenLSTM 的循环结构天然适合这种长度Transformer 的自注意力机制在短序列上优势有限反而因为参数量大在小数据集上更容易过拟合。pitg_model.py可以看成是在 LSTM 骨架上做的轻量扩展——加注意力机制但不引入大规模前馈网络。这个取舍让整套系统在单卡 GPU 上就能完成训练。这套系统从数据清洗到猜测曲线每一步都把你带向同一个问题你的模型到底为谁优化我记得第一次跑通全流程时draw_targuess_guessrate_comparison.py画出的两条曲线几乎重合我当时以为是模型没训练好后来才发现是评估脚本的 beam size 不一致。从那以后我每次跑评估都会强制走一遍“先用 query_analysis.py 抽查 20 条命中样本再统一 beam size最后画曲线”的流程再也没被可视化骗过。密码猜测这门玄学很多时候不是模型不行而是指标的刻度没校准。希望帮到你。本文还有配套的精品资源点击获取