资讯详情 基于Python的机器学习口令猜测评估:LSTM/Seq2Seq与猜测率曲线实战
📅 2026/10/12 13:25:08
简介基于Python的密码破解分析系统源码包面向密码安全研究与机器学习应用开发者系统通过LSTM与Seq2Seq模型分析真实口令猜测数据中的模式覆盖数据清洗、标准化、模型训练、评估与可视化完整流程。压缩包共74个文件包括60个Python脚本、6个Shell脚本、3张SVG示意图、2份TXT结果分析、2个JSON配置与1份README说明整体仅711KB目录结构清晰且轻量易部署。目前已有62位学习者下载使用。源码围绕口令破解路径生成、用户名编辑行为、猜测率对比等典型任务展开结合贪婪搜索与集束搜索算法评估命中率和召回率并提供对应可视化图形配合可扩展的数据读取与预处理模块适合需要落地深度学习口令分析实验的初学者以及希望快速复现序列到序列安全应用的技术人员。1. 基于 Python 的密码破解分析系统一套可复现的 ML 口令猜测评估链路这套基于 Python 的密码破解分析系统不是一个一键跑字典的恢复工具而是一条可复现的机器学习分析链路拿到一份密码样本后先用 LSTM 和 Seq2Seq 模型训练出猜测器再用贪婪搜索、集束搜索评估不同猜测次数下的命中率最后用 matplotlib 把结果画成猜测率曲线和 targuess 基线放到同一张图上对比。它能解决的问题很具体你的口令策略拦得住哪类猜测泄露样本里哪些密码是被「猜」出来的而不是被「爆」出来的规则一改命中率曲线怎么动这套系统就是回答这些问题用的。适合三类人做授权渗透测试与口令风险评估的工程师、研究口令猜测算法的学生、想给口令策略找量化依据的运维。前提是数据来源合法——授权研究样本、蜜罐日志或自造数据都可以别拿它打任何未授权目标。2. 先拆目录再动数据sample_data 到 dataset_analyze 的预处理全流程拿到压缩包别急着跑模型。整个 src 树分得很清楚baseline、analysis、draw、code、version、utils、config 各管一段先把职责边界摸清后面换数据、调参数才知道改哪个文件。第一步是装好环境Python 3.8 和 PyTorch其余依赖基本就是 numpy、matplotlib装完就能跑。2.1 目录拆解baseline、analysis、code、draw、version 各自负责什么我建议按下面这张表理解源码结构它对应的是「基线 → 数据分析 → 模型 → 评估 → 出图」的完整流水线。目录/前缀代表文件职责src/baselinetarguess、das对照基线TarGuess 是目标导向猜测基线das 是另一种统计式猜测基线src/analysisuser_pwd_behavior.py、user_edit_behavior.py用户名与密码关联、用户编辑密码行为的统计src/utilssample_data.py生成可复现的样本 CSV方便没有真实数据时先跑通src/drawanalyze_cracked_pwds_ri.py、draw_targuess_guessrate.py评估结果计算与出图src/codepitg_model.py、seq2seq_model.py、pitg_trainer.py模型结构、训练器、预处理src/versionpitg_cracker.py、query_analysis.py版本化评估与查询test 目录放的是回归测试analysis 目录里除了 .py 还有几个 .sh 入口比如 user_pwd_behavior.sh就是把统计脚本串起来一键跑批的config 目录下的 figure 放着 rnn_architecture.svg、credential_tweaking_attack.svg 这类用于论文或报告的示意图script 下是跑批用的 profile 脚本和 line.sh。数据流向是这样的sample_data.py 或你自己的 CSV 先进 dataset_analyze.py 做统计和清洗再交给 pitg_preprocess.py 转成训练集pitg_trainer.py 训练pitg_cracker.py 评估最后 draw 目录的脚本出图。所以新手跑通全流程的最低路径是sample_data → dataset_analyze → pitg_preprocess → pitg_trainer → pitg_cracker → draw。2.2 sample_data.py没有真实泄露数据时怎么造一份可用的样本 CSV真实泄露样本不是随便能拿到的授权研究的样本也往往因为格式问题要先洗一遍。这个项目在 utils 里放了 sample_data.py目的就是让你先拿合成数据把链路跑通。我一般会先跑它确认环境没缺依赖再换真实数据。# src/utils/sample_data.py —— 生成一份可复现的样本 CSV import csv import random import string COMMON_PASSWORDS [123456, password, admin, iloveyou, qwerty, abc123, welcome, letmein] def random_user(): return u .join(random.choices(string.ascii_lowercase string.digits, k8)) def random_password(): # 40% 概率直接落在常见弱口令里其余生成“字母数字”组合 if random.random() 0.4: return random.choice(COMMON_PASSWORDS) head .join(random.choices(string.ascii_lowercase, k6)) tail .join(random.choices(string.digits, krandom.randint(2, 4))) return head tail def generate(path, num2000, seed42): random.seed(seed) with open(path, w, encodingutf-8, newline) as f: writer csv.writer(f) writer.writerow([user, pwd, len, is_common]) for _ in range(num): pwd random_password() writer.writerow([random_user(), pwd, len(pwd), pwd in COMMON_PASSWORDS]) if __name__ __main__: generate(sample_pwds.csv)这段的逻辑是按行生成「用户名、密码、长度、是否常见弱口令」四列。seed42保证每次生成结果完全一致这对后续对比实验很关键——换参数前后数据不同结论就没法归因。is_common列是给后面分析用的你能直接看到弱口令在样本里占多少比例。想加大弱口令占比把0.4改成0.6即可。2.3 user_pwd_reader.py 与 dataset_analyze.py清洗、去重、截断的完整管线样本生成后真正干活的是 analysis 目录里的 reader 和 analyze 脚本。user_pwd_reader.py 负责把 CSV 读成内存里可用的「用户名-密码」对这块做不好后面 tokenizer 和模型全得跟着崩。# src/analysis/user_pwd_reader.py —— 读取并清洗用户名-密码对 def read_user_pwd_pairs(csv_path, sep,): pairs [] with open(csv_path, encodingutf-8-sig) as f: for line in f: line line.strip() if not line: # 跳过空行 continue fields line.split(sep) if len(fields) 2: # 字段不足的直接丢弃 continue user, pwd fields[0].strip(), fields[1].strip() if not user or not pwd: # 两侧任一侧为空就丢弃 continue if len(pwd) 64: # 超长密码截断防 OOV 和显存溢出 pwd pwd[:64] pairs.append((user, pwd)) return pairs读文件用utf-8-sig而不是utf-8是因为 Windows 下导出的 CSV 常带 BOM 头直接 utf-8 读会在第一列开头多出一个不可见字符后面统计 username 关联时全是脏数据。逻辑上共做了四道过滤空行、字段不足、空用户或空密码、超长密码截断。64 这个上限是我按密码实际分布取的绝大多数口令在 32 位以内留一倍余量给异常数据。dataset_analyze.py 就是在这个 reader 之上做统计输出 structure_analysis.txt 和 edit_analysis.txt。前者记录密码长度分布、字符集分布、同一个用户名下多个密码的复用率后者记录用户改密码时的编辑行为比如在旧密码基础上加数字、改大小写、替换字母。这些 txt 是给分析阶段看的中间产物也是后面训练集的字段设计依据。3. 核心模型训练LSTM/Seq2Seq 与 PITG 框架的启动方式数据洗干净后进入 code 目录。这里核心是两套模型一套是通用的 LSTM 编码器-解码器seq2seq_model.py另一套是 PITG 框架下的系列模块——pitg_model.py 定义模型结构pitg_trainer.py 是主训练器pitg_region_trainer.py 和 pitg_host_trainer.py 是按数据来源分别训练的变体。把模型当黑匣子跑当然可以但改参数时不知道结构会吃亏下面把关键环节拆开。3.1 tokenizer.py 与 password_dataset.py把密码字符串变成模型能吃的张量密码不是英文句子没有词的概念所以这里做成字符级词典。tokenizer.py 负责把每个字符映射到一个整数 ID并管理s、/s、pad、unk四个特殊标记。# src/code/tokenizer.py —— 字符级词典 import collections class CharTokenizer: BOS, EOS, PAD, UNK s, /s, pad, unk def __init__(self, max_len32): self.max_len max_len self.stoi {self.BOS: 0, self.EOS: 1, self.PAD: 2, self.UNK: 3} self.itos {v: k for k, v in self.stoi.items()} def fit(self, texts): counter collections.Counter() for s in texts: counter.update(s) for ch, _ in counter.most_common(5000): if ch not in self.stoi: idx len(self.stoi) self.stoi[ch] idx self.itos[idx] ch def encode(self, s): s s[:self.max_len] # 先截断再编码 ids [self.stoi[self.BOS]] ids [self.stoi.get(ch, self.stoi[self.UNK]) for ch in s] ids.append(self.stoi[self.EOS]) return idsfit按字符出现频次取前 5000超过这个量的生僻字符统一落进unk这样词典大小可控、训练时 embedding 表不至于太大。encode的顺序是 BOS 截断后的字符序列 EOS模型看到 BOS 才开始生成第一个输出字符。max_len32的取舍我解释一下口令类研究里超过 32 位的密码占比极低截断带来的信息损失可以忽略但能给训练和集束搜索省下大量显存和推理时间。password_dataset.py 则是把 tokenizer 封装进 PyTorch 的 Dataset返回(src_tensor, tgt_tensor, user_feat)三元组。user_feat 是可选输入crack_with_username 场景会用到普通训练可以置空。3.2 seq2seq_model.py 与 pitg_model.py编码器-解码器、注意力与集束搜索的关系模型结构是标准的 LSTM Seq2Seq编码器把密码字符序列压成隐状态解码器逐步生成输出字符每一步预测下一个字符的概率分布。pitg_model.py 在基础 Seq2Seq 上加了注意力机制让解码器在生成第 i 个字符时可以回看编码器各位置的隐状态这对「密码后半段是数字尾巴」这类局部模式非常有效。# src/code/seq2seq_model.py —— 编码器结构节选 import torch.nn as nn class Encoder(nn.Module): def __init__(self, vocab_size, emb_dim128, hidden_dim256): super().__init__() # padding_idx2 对应 tokenizer 里的 pad保证 pad 位置不更新 embedding self.embedding nn.Embedding(vocab_size, emb_dim, padding_idx2) self.lstm nn.LSTM(emb_dim, hidden_dim, batch_firstTrue) def forward(self, x): emb self.embedding(x) out, (h, c) self.lstm(emb) return h, cemb_dim128、hidden_dim256是我在这个数据集上试下来性价比最高的组合。密码序列很短模型再大收益有限反而更容易过拟合到训练集的弱口令模式上。注意padding_idx2必须和 tokenizer 里 PAD 的索引一致否则 padding 位置会参与梯度更新等于凭空加了噪声。解码器和训练用的是 teacher forcing训练时把真实目标序列作为解码器输入而不是把上一步的预测结果喂回去。这样收敛快很多但评估时必须切到自回归模式只给 BOS让它自己一步步生成——评估逻辑在 pitg_evaluator.py 里这也是代码里 greedy 和 beam 两种策略的切换点。3.3 pitg_trainer.py / pass2path_trainer.py两阶段训练的参数与启动方式PITG 框架的训练分两个阶段。第一阶段 pitg_trainer.py 训练直接从密码样本学习分布的猜测器第二阶段 pass2path_trainer.py 训练的是「从一个已猜到的基础密码逐步编辑到目标密码」的路径模型配合 generate_inplace_edits.py 和 edit_path.py 使用。两阶段分开跑的好处是第一阶段模型负责候选集生成第二阶段负责在候选集内做精排评估时能单独看每一层的命中率贡献。# src/code/pitg_trainer.py —— 训练主循环节选 import torch import torch.nn as nn def train_epoch(model, loader, optimizer, criterion, clip1.0): model.train() total_loss 0.0 for src, tgt in loader: src, tgt src.to(device), tgt.to(device) logits model(src, tgt) # (B, T, vocab_size) loss criterion(logits.view(-1, logits.size(-1)), tgt.view(-1)) optimizer.zero_grad() loss.backward() nn.utils.clip_grad_norm_(model.parameters(), clip) optimizer.step() total_loss loss.item() return total_loss / len(loader)训练循环里有三个值得注意的点。第一clip_grad_norm_(..., 1.0)是 LSTM 训练的必选项梯度裁剪能压住长序列反向传播时的梯度爆炸不加的话 loss 经常在某个 epoch 突然变成 NaN。第二logits.view(-1, logits.size(-1))把 batch 和序列长度两个维度合并成 N和tgt.view(-1)对齐后再算交叉熵这是 Seq2Seq 里最常见的写法漏了这个维度对齐loss 算出来永远是错的。第三优化器一般选 Adam学习率 1e-3batch_size 128评估阶段的 beam 宽度我在这个项目里常用 10太大推理太慢太小和 greedy 拉不开差距。跑训练的时候可以直接用 config/script 下的 shell 脚本串起来user_pwd_behavior.sh 这类入口会把预处理、训练、评估按顺序执行日志打到控制台。第一次跑建议先只训 5 个 epoch 验证链路通不通再放开到完整轮次。4. 评估与可视化贪婪搜索、集束搜索与猜测率曲线的口径对齐模型训完不等于知道它好不好。口令猜测任务的核心指标不是 loss而是「给定 X 次猜测机会能覆盖多少目标密码」——这个数叫猜测率和传统机器学习的 accuracy 完全不是一个概念。draw 目录下的 analyze_cracked_pwds_ri.py、analyze_cracked_pwds_ed.py、draw_targuess_guessrate.py 就是在算这个并画图。4.1 评估口径rank index、命中率与召回率怎么对上先说明三个容易混的指标。rank indexRI是目标密码在模型猜测列表里的真实排名排第 100 就是 rank 100命中率hit rate是「rank 小于等于 N」的测试样本占比猜测率曲线则是横轴取 N猜测次数纵轴取累计命中率。analyze_cracked_pwds_ri.py 做的是 rank 统计analyze_cracked_pwds_ed.py 做的是编辑距离维度的分析——把猜错的样本按与真实密码的编辑距离分组能看出模型「差几个字符」就能猜中。指标计算方式用途rank index目标密码在生成列表中的名次单条样本的猜测难度命中率Nrank ≤ N 的样本占比给定预算下的覆盖能力猜测率曲线N 取 1e2~1e7 时的累计命中率与基线对比的主图一个常见误区是把「模型生成的前 1000 条里包含目标密码」和「模型 rank 前 1000」混为一谈。生成列表如果是带重复、带未去重的 beam 展开结果前 1000 条去重后可能只剩 400 条命中率直接虚高一倍。评估前必须统一做去重再算 rank。4.2 draw_targuess_guessrate.py猜测率曲线怎么画、参数怎么改这个脚本负责把评估结果画成对数坐标下的猜测率曲线。口令猜测的搜索空间动辄 1e6 到 1e10画线性坐标前段曲线会糊成一团所以横轴固定用 log 刻度。# src/draw/draw_targuess_guessrate.py —— 猜测率曲线出图节选 import matplotlib.pyplot as plt def load_result(path): # 每行: guess_num, hit_rate例如 1000,0.081 nums, rates [], [] with open(path) as f: for line in f: g, r map(float, line.strip().split(,)) nums.append(int(g)) rates.append(r) return nums, rates def plot_guessrate(guess_nums, hit_rates, label, ax): ax.loglog(guess_nums, hit_rates, markero, labellabel) ax.set_xlabel(number of guesses (log)) ax.set_ylabel(cracked ratio) ax.grid(True, whichboth, ls--, alpha0.5) ax.legend() def main(): fig, ax plt.subplots(figsize(8, 6)) nums, rates load_result(results/ours.csv) plot_guessrate(nums, rates, PITG beam10, ax) plt.savefig(guessrate.png, dpi150)load_result读的是两列 CSVguess number 必须单调递增。loglog两个坐标轴都取对数曲线抬得越高说明同样猜测次数下覆盖越多模型越好。想对比不同 beam 宽度时多调几次plot_guessrate叠在同一个 ax 上即可代码里 draw_targuess_guessrate_comparison.py 就是干这个的。4.3 示例输出同一模型 greedy 与 beam 的差异怎么看跑完对比后你通常会看到这样的读数规律beam 宽度从 5 提到 25前 1e5 次猜测的命中率能提升 3 到 8 个百分点但推理时间接近线性增长。如果 beam10 和 beam25 的曲线几乎重合说明模型本身候选质量是瓶颈继续加 beam 只是浪费算力如果两条曲线明显分开说明生成阶段有多样性不足的问题该回头调训练数据或模型结构而不是无脑加宽 beam。提示评估时集束搜索的显存开销大约是贪婪搜索的 beam 倍。不要一次性把整个测试集塞进 beam 解码按 batch 拆块跑否则很容易在最后一步 OOM前面几个小时的训练白费。5. 避坑指南密码数据、显存与评估口径的五个翻车现场这个项目我前后复现过两遍第一遍卡在数据清洗第二遍卡在评估口径。下面五条是踩得最实、也最容易被新手忽略的坑每条都按「现象 → 原因 → 解决」给出你照着对照检查即可。5.1 tokenizer 撞上空密码和超长密码直接抛异常现象跑 pitg_preprocess.py 时抛 IndexError 或 UnicodeDecodeError位置在 tokenizer 的self.stoi[ch]训练进程直接中断。原因样本 CSV 里有空行、空密码或者密码里带了不可见字符枚举字符时命中了 tokenizer 里不存在的键而超长密码把序列撑爆了 max_len 的预期。解决在 reader 层做四道过滤——跳过空行、字段不足的直接丢弃、user 和 pwd 任一侧为空就丢弃、超过 64 字符截断。tokenizer 的encode里再强制s[:self.max_len]兜底一次双保险。我第一次就是只加了一层过滤结果测试集里混进来几行手滑数据整个训练重跑。5.2 GPU 显存不足batch_size 一调就 OOM现象训练前几个 epoch 正常到评估阶段跑 pitg_cracker.py 时直接CUDA out of memory或者训练后期显存稳步上涨直到溢出。原因两个独立问题。评估阶段 beam search 会把每一条候选展开成 beam 条序列显存占用是贪婪搜索的 beam 倍训练阶段如果用了变长序列且没有按长度 bucketingbatch 里最长的那条会把整个 batch 的 padding 拉到它的长度。解决评估时按 batch 拆块每块 64 或 128 条跑完一块再进下一块训练时按序列长度分组相近长度的密码放同一个 batchpadding 量能省 30%~50%。显存实在不够就把hidden_dim从 256 降到 128密码任务对这种降维不敏感。5.3 命中率虚高训练集和测试集切分没按用户维度现象测试集命中率 45%你觉得模型很强换到另一批新样本上命中率掉到 15%。原因数据切分默认是随机按行切同一个用户的多条密码被拆进训练集和测试集。模型在训练时见过该用户的「密码风格」测试时等于开卷考试这叫数据泄露。解决按用户维度切分。先按 user 分组再把整组用户分到训练或测试保证同一个 user 的所有记录只进一侧。改成这样之后我的命中率从 45% 掉到接近真实水平但这个数字才是对外敢报的数。5.4 评估口径不一致rank 和 guess number 混着用现象你报命中率 30%基线作者报 45%两边代码逻辑看起来一样数字却对不上。原因一方的「前 N 次猜测」是去重后的生成列表另一方是去重前的 beam 展开列表或者一方 rank 按百分位算另一方按绝对次数算。解决统一口径。我一般固定用「去重后的生成列表前 1e6 次猜测的覆盖比例」作为对外对比指标rank index 只在单样本分析里用。在 README 或实验记录里把「去重、猜测次数上限、是否包含训练集」三件事写死同一批对比实验全部走同一个入口脚本。5.5 CSV 编码与中文用户名乱码现象Windows 下导出的 CSV 读进来后username 字段第一个字符变成\ufeff之类不可见符号user 关联统计全错。原因文件是 GBK 或带 BOM 的 UTF-8而代码里默认用 utf-8 打开BOM 头被当成字符读进来了。解决reader 统一用encodingutf-8-sig它能自动吞掉 BOM。如果还是乱码说明源文件是 GBK用encodinggbk或errorsreplace打开把乱码行标记出来单独清洗。中文用户名本身不是问题问题是编码编码统一后crack_with_username 里按用户名做特征才不会出幺蛾子。6. 进阶用法带用户名的定向猜测与编辑路径模型验证跑通基础链路后这个项目真正的价值在定向猜测。现实中攻击者不会均匀地猜所有密码而是盯着用户名、邮箱前缀做变形——在密码后补年份、把用户名首字母大写、替换成 Leetspeak这就是 config/figure 里 credential_tweaking_attack.svg 画的那个攻击场景。研究它不是为了替攻击者解锁而是为了量化这种攻击的覆盖能力从而设计防御规则。6.1 crack_with_username.py 的调用方式python crack_with_username.py \ --model checkpoints/pitg_best.pt \ --user_pwd sample_pwds.csv \ --topk 100 \ --beam 10 \ # 注意仅用于授权的口令安全研究--beam 10控制解码宽度--topk 100表示每个用户名输出排名前 100 的猜测。脚本会把 user 特征拼进解码器的初始状态让模型优先生成与用户名相关的候选。跑完后输出每一条的 user、guessed_pwd、rank直接和 baseline 对比。6.2 用编辑路径模型做同条件对比验证python draw_targuess_guessrate_comparison.py \ --ours results/ours_username.csv \ --baseline results/targuess.csv \ --out comparison.png对比脚本强制两条曲线共享同一个 guess number 横轴这是验证模型有没有真实增益的关键一步。曲线在 1e4~1e6 区间明显高于 targuess说明定向特征确实起了作用如果只在 1e7 之后才追上那只是暴力空间堆出来的没有实际价值。从那以后我每次跑完一组密码分析实验都强制走一遍三件事确认切分是按用户维度做的、统一评估口径到同一个 guess number、把基线和模型的输入数据大小对齐。这三步省掉过太多返工。这套源码包建议直接拉下来先用 sample_data.py 造数据跑通全流程再换成你自己的合法样本调参。希望帮到你。本文还有配套的精品资源点击获取