密码破解分析系统:Seq2Seq/PITG建模与guessrate评估

发布时间:2026/10/19 11:28:19
密码破解分析系统:Seq2Seq/PITG建模与guessrate评估 简介一套基于 Python 语言编写的密码破解分析系统源码面向计算机安全方向的研究者与机器学习爱好者聚焦如何借助深度学习模型剖析密码破解数据、发现解密规律并提升破解效率。压缩包内共 74 个文件以 60 个 Python 脚本为主同时包含 6 个可执行 shell 脚本、3 张 SVG 架构示意图、2 份文本分析记录、2 个 JSON 配置以及 1 个说明文档整体仅 711 KB目录划分清晰便于按模块查阅。系统实现了数据清洗、分割与标准化并集成 LSTM、Seq2Seq 两种常用序列模型完成训练通过贪婪搜索和集束搜索算法计算命中率、召回率等指标再借助 matplotlib 进行可视化对比。另包含用户编辑行为统计、猜测率变化曲线、密码相似度分析等脚本可辅助深入理解密码破解的攻击特征。已有 62 人学习下载适合有一定 Python 与 PyTorch 基础、希望将序列建模应用到安全分析场景的中高级学习者通过源码可快速复现实验并扩展算法调优。1. 密码破解分析系统把弱口令的“被命中路径”变成可复现实验某公司做内部弱口令自查时安全组拿到一批脱敏的账号和密码样本想回答一个很实际的问题如果站在攻击者视角用户的密码会不会在“猜前 1000 次”之内被命中人工排序不现实靠经验拍脑袋又不服众。这个基于 Python 的密码破解分析系统核心价值就是把“密码被猜中”拆成可复现的实验先做数据清洗和抽样再用 LSTM、Seq2Seq、PITG 模型把密码变换建模成序列生成任务通过贪婪搜索和 Beam Search 算出命中率与召回率最后用 matplotlib 画成 guessrate 对比曲线。它适合做口令安全评估、风控策略验证也适合想理解序列生成模型在真实数据集上怎么落地的后端或算法开发者。注意这套代码是安全研究用的分析框架不是让你拿真实目标测试攻击工具。2. 目录结构梳理从原始数据到模型训练代码到底怎么摆放2.1 顶层目录划分我拿到压缩包后第一件事就是看目录。整个工程不是单脚本堆砌而是按“数据读入 → 行为分析 → 模型训练 → 评估可视化 → 脚本编排”分层的。顶层大致长这样项目根目录/ ├── README.md ├── src/ │ ├── baseline/ │ │ └── targuess/ │ ├── das/ │ └── analysis/ │ ├── user_pwd_behavior.py │ ├── username_edit_behavior.py │ ├── user_edit_behavior.py │ ├── user_pwd_reader.py │ ├── user_edit_reader.py │ ├── dataset_analyze.py │ ├── structure_analysis.txt │ └── edit_analysis.txt ├── code/ │ ├── tokenizer.py │ ├── password_dataset.py │ ├── seq2seq_model.py │ ├── pitg_model.py │ ├── pitg_trainer.py │ ├── pitg_evaluator.py │ ├── pitg_cracker.py │ ├── pitg_region_trainer.py │ ├── pitg_host_trainer.py │ ├── pass2path_trainer.py │ ├── pass2path_evaluator.py │ ├── generate_path.py │ ├── generate_inplace_edits.py │ ├── edit_path.py │ ├── crack_with_username.py │ └── version/ ├── script/ │ ├── user_pwd_behavior.sh │ ├── user_pwd_behavior_demo.sh │ ├── user_behavior.sh │ ├── user_pwd_behavior2.sh │ └── user_behavior2.sh ├── profile/ └── figure/ ├── rnn_architecture.svg ├── email.svg └── credential_tweaking_attack.svg从结构能读出设计意图。src/analysis/放的是人对密码和用户名的行为分析脚本目标是产出一批结构化统计文件比如structure_analysis.txt、edit_analysis.txt。src/baseline/targuess/是统计基线用来证明后面训练的模型比规则式猜测强多少。src/das/和analysis高度耦合主要是分析模块的数据访问辅助老代码里经常能看到两者互相 import。真正的模型主战场在code/。tokenizer、dataset、seq2seq、pitg、pass2path 全在这里version/保存历史 checkpoint 和冻结版本方便回归对比。script/是 shell 编排把分析和训练串起来保证换机器还能一键复现。figure/放输出图profile/是运行环境相关的 profile 配置。我习惯先把script/下的.sh读一遍因为它们才是最接近“怎么运行”的答案。比如user_pwd_behavior.sh会依次调用dataset_analyze.py、user_pwd_behavior.py再用user_behavior.sh汇总画图。只要 shell 脚本没坏整个流程就能走通。2.2 数据读入与抽样这两个文件决定了实验能不能复现src/analysis/user_pwd_reader.py是数据入口。它负责把不同格式的 CSV 统一成user和pwd两列。我看到的实现是按列名映射缺失行直接按空字符串过滤。import pandas as pd COL_MAP { username: user, user: user, name: user, account: user, password: pwd, pass: pwd, pwd: pwd } def read_user_pwd(path: str) - pd.DataFrame: df pd.read_csv(path, keep_default_naFalse) df df.rename(columnsCOL_MAP) df df[[user, pwd]] df df[(df[user].str.len() 0) (df[pwd].str.len() 0)] df df.drop_duplicates(subset[user, pwd]) return df.reset_index(dropTrue)这段代码解决的是脏数据问题。参数说明keep_default_naFalse会把 CSV 里的空字符串保留下来而不是转成 NaN便于后面按字符串长度过滤drop_duplicates(subset[user, pwd])按“用户 密码”去重避免同一个口令被统计两次。实际跑的时候列名可能是account passwd或phone password所以映射表必须能扩展。抽样逻辑在src/utils/sample_data.py。我发现压缩包里专门放一个sample_data.py说明作者对这种长尾分布考虑得很清楚如果不限制每个用户最多保留多少密码高频用户的习惯会在训练集里失衡模型会偏向“人数多但习惯单一”的用户群体。import random import pandas as pd def sample_by_user(df: pd.DataFrame, max_per_user: int 20, seed: int 42) - pd.DataFrame: rng random.Random(seed) outputs [] for user, group in df.groupby(user, sortFalse): if len(group) max_per_user: outputs.append(group) else: outputs.append(group.sample(max_per_user, random_staterng)) return pd.concat(outputs, ignore_indexTrue)这里的关键参数是max_per_user设太小长尾用户学不到设太大高频用户主导 loss。我一般会先用dataset_analyze.py看每个用户的密码数量分布再决定这个阈值。seed是复现命脉跑实验之前先固定住否则第二天改代码后结果对不上很容易误判模型改进点。2.3 行为分析编辑距离、用户名编辑和结构特征src/analysis/里有一批行为分析脚本user_edit_behavior.py和username_edit_behavior.py分别研究“密码到密码的编辑”和“用户名到密码的编辑”。它们输出的edit_analysis.txt和structure_analysis.txt不是给人看的报表而是后面模型挑选训练样本的依据。比如structure_analysis.txt会统计每个密码的长度、字符种类、是否包含数字段、是否出现键盘连续序列。edit_analysis.txt则计算编辑操作分布替换、插入、删除、大小写变换各自占多少比例。这些统计结果会直接告诉我们PITG 模型的编辑路径到底应该侧重哪类操作。user_edit_reader.py和user_pwd_reader.py的区别也在这里前者读的是“用户名 密码”结构后者读的是“用户 id 密码”。前者更贴近真实攻击视角因为攻击者通常会拿用户名做种子。这也是后面crack_with_username.py存在的意义。3. 核心模型拆解Seq2Seq、PITG 与 pass2path 的生成链路3.1 字符级 tokenizer 和数据集的坑code/tokenizer.py是所有模型的第一关。它只做字符级 token不切词。密码是由数字、字母、符号组成的无空格字符串词级 tokenizer 在密码场景基本不适用字符级才是常见做法。class CharTokenizer: def __init__(self, vocab: str): self.vocab vocab self.stoi {c: i for i, c in enumerate(vocab)} self.itos {i: c for i, c in enumerate(vocab)} self.pad_idx self.stoi[pad] self.sos_idx self.stoi[sos] self.eos_idx self.stoi[eos] def encode(self, seq: str): ids [self.sos_idx] for c in seq: if c in self.stoi: ids.append(self.stoi[c]) ids.append(self.eos_idx) return ids def decode(self, ids): chars [self.itos[i] for i in ids] if self.eos_idx in chars: chars chars[:chars.index(self.eos_idx)] return .join(c for c in chars if c ! sos)参数说明vocab必须覆盖训练集里所有字符否则推理时遇到out-of-vocab字符就会崩或者在预处理阶段被静默丢掉。这里用 skip 而不是 unk是因为密码里低频符号丢掉影响较小但pad、sos、eos三个控制符必须保留且索引不能乱。数据集实现是code/password_dataset.py它负责把变长的 token id 补成定长 tensor。from torch.utils.data import Dataset import torch class PasswordDataset(Dataset): def __init__(self, src_pwds, tgt_pwds, tokenizer, max_len32): self.src_ids [tokenizer.encode(p)[:max_len] for p in src_pwds] self.tgt_ids [tokenizer.encode(p)[:max_len 1] for p in tgt_pwds] def __len__(self): return len(self.src_ids) def __getitem__(self, idx): src torch.tensor(self.src_ids[idx], dtypetorch.long) tgt torch.tensor(self.tgt_ids[idx], dtypetorch.long) return src, tgt这段代码的坑在于max_len截断发生在 tokenizer 输出之后如果某个密码本来有 40 个字符截断后eos会被切掉训练时模型永远学不到终止符。我一般会在截断之后手动补eos或者把max_len设到密码长度的 90 分位以上宁可用显存换稳定性。3.2 Seq2Seq 模型LSTM 编码器解码器code/seq2seq_model.py提供了序列生成的主干。编码器把源密码编码成隐状态解码器逐个 token 输出目标密码。核心实现简化后长这样。import torch import torch.nn as nn class Seq2Seq(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim256, num_layers2, dropout0.2): super().__init__() self.embed nn.Embedding(vocab_size, embed_dim, padding_idx0) self.encoder nn.LSTM(embed_dim, hidden_dim, num_layers, batch_firstTrue, dropoutdropout) self.decoder nn.LSTM(embed_dim, hidden_dim, num_layers, batch_firstTrue, dropoutdropout) self.fc nn.Linear(hidden_dim, vocab_size) def forward(self, src, tgt): src_emb self.embed(src) enc_out, (h, c) self.encoder(src_emb) tgt_emb self.embed(tgt) dec_out, _ self.decoder(tgt_emb, (h, c)) logits self.fc(dec_out) return logits逻辑说明这里把编码器最终隐状态(h, c)直接传给解码器源密码信息从解码第一步开始参与生成。batch_firstTrue表示输入形状是(batch, seq_len, embed_dim)PyTorch LSTM 默认是seq_len在第一位漏掉这个参数会导致维度错乱。padding_idx0让pad在 embedding 层梯度恒为 0避免补齐 token 污染训练。参数说明hidden_dim256对密码这种长度不超过 20 的序列够用embed_dim一般 128 起步太小会让相似拼音、键盘序列之间没有可分性太大在小数据集上过拟合。训练时如果用 teacher forcing目标侧输入是真实目标密码而不是上一步预测收敛快很多但推理时就要换成自回归循环。figure/rnn_architecture.svg画的就是这套结构。我二次开发时会把这张图跟seq2seq_model.py对照看能快速理解(h, c)从哪一层传进去、哪一层输出接全连接层。3.3 PITG 模型把密码变换建模成“编辑路径”code/pitg_model.py是这套工程里我认为最有价值的部分。传统 Seq2Seq 直接生成目标密码而 PITG 的思路是先生成一条编辑路径从原始密码出发每一步是copy、replace、insert、delete中的一种。这样模型不用记住密码字符的具体分布只需要学会“什么位置做什么操作”数据量要求低解释性也强。code/edit_path.py负责把src和tgt对齐成编辑路径。下面是一个简化版实现。def compute_edit_path(src: str, tgt: str): ops [] i j 0 while i len(src) or j len(tgt): if i len(src) and j len(tgt) and src[i] tgt[j]: ops.append((copy, src[i])) i 1 j 1 elif j len(tgt) and i len(src): ops.append((replace, src[i], tgt[j])) i 1 j 1 elif j len(tgt): ops.append((insert, tgt[j])) j 1 else: ops.append((delete, src[i])) i 1 return ops这个贪心对齐只做参考真正的训练样本来自generate_inplace_edits.py它枚举“原地编辑”操作来构造正负样本而不是完全依赖简单的对齐算法。pitg_model.py会把编辑操作序列 token 化然后用 LSTM 或 Transformer 预测下一个操作。参数上要关注ops_vocab如果操作类型不全比如漏了大小写替换模型就学不会zhangwei到ZhangWei这种常见变换。pitg_region_trainer.py和pitg_host_trainer.py是训练器两种变体前者按密码区域如数字段、字母段分别训练强调局部模式后者用数据集里所有用户信息一起训练强调全局习惯。我在实际跑的时候发现region 版在小数据集上更容易收敛但泛化到没见过的用户名时会差一些。3.4 把用户名卷进来的猜测入口code/crack_with_username.py是面向效果的入口它把“用户名”当成密码生成的条件。比如用户名叫zhangwei密码很可能是zhangwei123、ZhangWei!#、zw123456。这个逻辑单靠 Seq2Seq 也能学但显式建模成 username-to-password 会让命中率更可解释。src/baseline/targuess/提供的是统计基线。它会根据用户名、邮箱前缀、数字尾巴做模板猜测比如检测到用户名后拼接年份和常见符号。训练模型的 guessrate 要和这套基线对比才有意义否则你不知道模型提升是来自数据集的偶然性还是真的学到了编辑规律。pitg_cracker.py则负责把训练好的模型应用到候选生成给一批用户名和可能的原始密码批量生成候选密码再按概率排序输出。这个文件是评估脚本和真实猜测之间的桥梁。4. 训练与评估loss、Beam Search 和 guessrate 曲线背后4.1 训练器loss 计算和参数配置pass2path_trainer.py和pitg_trainer.py的结构相似区别在数据和模型。通用训练循环如下。for epoch in range(epochs): for batch in train_loader: src, tgt batch optimizer.zero_grad() logits model(src, tgt[:, :-1]) loss loss_fn(logits.reshape(-1, vocab_size), tgt[:, 1:].reshape(-1)) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step()逻辑说明tgt[:, :-1]是解码器输入tgt[:, 1:]是目标输出这是标准 teacher forcing 写法。reshape(-1, vocab_size)合并 batch 和序列维度方便计算交叉熵。max_norm1.0的梯度裁剪很关键LSTM 训练时偶尔出现 loss 突变成 NaN大部分原因是梯度爆炸。参数说明epochs10、lr1e-3、batch_size128是常见起点。日志里的 loss 如果前两个 epoch 没从 4.5 降到 3.0就该检查是否用了错误的padding_idx或者数据集的 tokenizer 没把eos算进 vocab。pitg_evaluator.py和pass2path_evaluator.py是评估入口它们不参与训练而是加载 checkpoint 后跑候选生成。配置都在config/目录下我改参数时主要改三个模型结构配置、数据路径、评估的beam_size。4.2 贪婪搜索与 Beam Search命中率和召回率从哪来代码里有draw_targuess_guessrate.py说明模型评估的核心指标是 guessrate。guessrate 表示“生成前 N 个候选里能覆盖多少真实密码”。贪婪搜索只取每个时刻概率最大的 token表现通常很差Beam Search 保留 top K 条部分序列最后按累计对数概率排序。def beam_search(model, src, tokenizer, beam_size5, max_len20): with torch.no_grad(): src_emb model.embed(src.unsqueeze(0)) enc_out, (h, c) model.encoder(src_emb) sequences [[tokenizer.sos_idx, 0.0]] for _ in range(max_len): all_candidates [] for seq, score in sequences: if seq[-1] tokenizer.eos_idx: all_candidates.append((seq, score)) continue input_tok torch.tensor([seq[-1]]).unsqueeze(0) logits model.decode_step(input_tok, h, c) probs torch.log_softmax(logits, dim-1).squeeze(0) for idx, p in zip(*torch.topk(probs, beam_size)): all_candidates.append((seq [idx.item()], score p.item())) sequences sorted(all_candidates, keylambda x: x[1], reverseTrue)[:beam_size] return sequences[0][0]这里需要注意torch.topk的返回值。probs是vocab_size的一维向量zip(*torch.topk(probs, beam_size))会同时取出分数和索引。beam_size不是越大越好调大 beam 能提升召回率但会把生成时间放大 beam 倍在小数据集上还会让同一模板的候选大量重复guessrate 曲线反而变稀疏。指标上代码里的命中率一次只计算“当前这批候选是否包含目标密码”和“全部历史候选合计命中”是两回事。我见过的翻车现场大多是直接把每次实验的 guessrate 相加导致最终曲线超过 1.0看着很强实际是口径错了。4.3 可视化脚本对比曲线是给谁看的draw_targuess_guessrate_comparison.py、draw_targuess_guessrate.py、draw_guessrate_via_editdistance.py都是画图。前者比较不同模型后者按编辑距离分组看命中率比如距离为 1、2、3 的密码分别命中多少。import matplotlib.pyplot as plt def draw_guessrate(curves, labels, out_path): x [i 1 for i in range(len(curves[0]))] fig, ax plt.subplots(figsize(8, 5)) for curve, label in zip(curves, labels): ax.plot(x, curve, marker., labellabel) ax.set_xscale(symlog) ax.set_xlabel(number of guesses (log)) ax.set_ylabel(guess rate) ax.legend() fig.savefig(out_path, dpi150, bbox_inchestight)注意这里用xscale(symlog)而不是log因为 guessrate 前几名可能为 0log会把 0 值直接画出空点。dpi150是为论文或报告准备bbox_inchestight避免 SVG 输出时裁掉图例。这个脚本本身不训练模型只消费评估器生成的 CSV所以我可以反复调样式而不污染实验结果。5. 常见问题与避坑把这套代码跑起来时我踩过的几个坑5.1 tokenizer 字符表不一致训练出来全是unk现象模型 loss 能降但 decode 出来的密码大多是soseos或重复的单个字符。原因训练时用的 tokenizer 是从训练集生成的评估时又加载了另一个 vocab 文件两边stoi不一致模型预测的索引在推理 vocab 里对应完全不同的字符。解决固定一个vocab.json文件。我会在code/version/里放一份构建好的字符表并在启动脚本里用绝对路径引用避免.py文件默认相对路径在不同工作目录下读到不同文件。5.2 编辑路径生成出现死循环候选密码长度失控现象PITG 模型生成的候选是abcabcabc...越来越长或者同一个字符插入几百次评估阶段内存被打满。原因generate_inplace_edits.py把 insert 操作放在任何位置模型学到 insert 可以无限执行如果解码时不强制终止copy/insert会互相交替。解决给 decode 加长度上限和终止约束。我一般限制生成密码长度不超过原始长度的 1.5 倍并在生成到eos时强制退出处理 insert 时限制连续 insert 次数不超过 3 次。5.3 script 里的相对路径找不到数据集现象在项目根目录跑sh script/user_pwd_behavior.sh时脚本报FileNotFoundError: data.csv但文件明明存在。原因脚本开头如果用了cd $(dirname $0)/..从非项目根目录调用时dirname $0拿到的是相对路径切过去的目录不对。解决每个.sh开头加cd $(dirname $0)/..并pwd验证然后把数据路径通过环境变量DATA_DIR传入不让脚本硬编码相对路径。5.4 GPU 显存溢出batch size 一调就炸现象训练跑几个 step 之后CUDA out of memory。原因Seq2Seq 的max_len32如果 batch 里有大量长密码PyTorch 没做长度打包所有序列都补齐到 32embedding 和 LSTM 的显存占用比预期高很多。解决先确认torch.backends.cudnn.benchmark False排除 cuDNN 问题然后按长度分桶。我会写一个简单函数按密码长度排序分组让每个 batch 内长度接近显存占用能明显下降。5.5 guessrate 曲线累计方式搞错结果看起来百发百中现象同一份模型在draw_targuess_guessrate.py里曲线很漂亮但换到analyze_cracked_behavior.py里命中率数值对不上。原因一个脚本算的是累计命中率另一个脚本算的是单 batch 命中率。guessrate 严格来说是“在前 N 次候选内命中的比例”必须把所有历史候选合起来算而不是只算当前窗口。解决先明确指标定义。我在评估输出里同时打印cumulative和per_batch两个数值画图只消费 cumulative 字段。6. 二次开发建议改成你自己的口令评估工具6.1 接入新数据集要改哪几个文件如果只是想在自己的脱敏数据上复现实验我一般只改三个地方user_pwd_reader.py里的COL_MAP、sample_data.py里的max_per_user、config/下的数据路径。其余脚本尽量不动。先跑通再优化而不是一次改一堆。6.2 把 LSTM 换成 Transformer 的改造思路seq2seq_model.py当前是 LSTM。如果想换注意力机制我会保留CharTokenizer和PasswordDataset替换模型主体为 Transformer encoder-decoder同时把crack_with_username.py里的 decoder 循环改成顺序生成。注意两点Transformer 对位置编码很敏感密码长度不固定建议用正弦位置编码不要直接训练绝对位置 embedding批量推理时要用 mask 保证未来 token 不可见。6.3 验证小技巧先跑 baseline再改模型最后是验证方法。我每次改完代码不会直接上完整训练而是先跑baseline/targuess和version/里保存的旧 checkpoint确认draw_targuess_guessrate.py能画出和 README 一致的曲线。判断“这个改动有效”必须用同一条 guessrate 指标对比而不是只看 loss。以前我拿到新数据集总是急着调参结果在structure_analysis.txt的统计口径没统一的前提下改了一晚上参数第二天发现 baseline 的 guessrate 因为抽样方式变了也跟着涨。从那以后我每次换数据集或改数据预处理都强制走一遍version/里的回归脚本确认统计口径没变再动模型。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询