资讯动态

口令破解大作业技术路线选型:统计规则、PCFG与机器学习对比实战

发布时间:2026/10/21 22:37:34 来源:尧图企业网站定制
简介这份资源是国科大Web安全技术课程口令破解方向的大作业参考代码面向正在修读该课程或自学口令安全与机器学习结合实践的学生。内容围绕口令特征提取、特征统计与暴力猜测三条主线展开覆盖从数据预处理到模型训练、结果分析的完整流程适合具备Python基础、希望理解真实口令数据集处理与攻击思路的中高级学习者。压缩包共155个文件约170.54MB以78个txt与16个csv承载原始口令及统计结果15个py脚本实现特征分析与猜测逻辑另有xlsx统计表、png图表及少量配置与版本控制文件目录结构便于按模块查阅。目前已有362人学习下载。读者可从中获得可运行的口令特征工程代码、针对csdn与yahoo数据集的统计脚本、暴力猜测实现及配套分析图表用于课程作业参考、实验复现与排错思路借鉴。1. 口令破解大作业为什么我劝你别一上来就上机器学习口令破解这个大作业我前后帮三届人看过代码发现一个特别反直觉的现象用机器学习的那批人最后交出来的东西反而最单薄。不是机器学习不行是大部分人把它当成了万能锤子拿到数据集就train_test_split跑完一个分类器准确率 92%然后呢然后就没有然后了——老师问一句「你这 92% 到底比规则方法强在哪」直接哑火。口令破解这个题目本质是「给定一批泄露口令推断生成规律再按规律造出能命中的新口令」。它有三条完全不同的技术路线纯统计规则HashCat 规则、John 的增量模式、概率模型PCFG、Markov、机器学习RNN、GAN、Transformer。三条路线的工程量、可解释性、命中率曲线完全不一样。这篇就把这三条路线拆开对比告诉你什么情况下该选哪条以及每条路最容易翻车的地方在哪。如果你正在做这个作业或者想真正搞懂口令猜测的底层逻辑这篇能帮你省掉至少两周的无效试错。2. 三条技术路线怎么选从四个维度给结论选路线之前先明确一件事这个作业的评分点通常不在「准确率」上而在「你能不能解释清楚你的模型为什么能生成有效口令」。我见过太多人把重点放反了花一周调参结果报告里连 PCFG 的概率公式都写不明白。2.1 先搞清楚你的数据集长什么样不同数据集决定了你能走哪条路。常见的几类数据集类型典型规模特点适合的路线小规模泄露集1万10万条噪声大、重复多统计规则中等规模10万100万条有结构规律PCFG / Markov大规模100万条以上覆盖广、长尾明显机器学习带个人信息不定含用户名、生日等规则模板混合如果你拿到的数据集只有几万条硬上 RNN训练集都不够模型收敛最后生成的口令全是训练集里的原样复制这种「过拟合式抄袭」在答辩时一问就穿帮。我一般会先做一件事统计数据集的口令长度分布、字符集分布、是否有明显的结构模式比如「单词数字」「拼音特殊符号」。这个统计结果直接决定路线选择。2.2 四个维度的逐项对比把三条路线拉到同一张表里比维度统计规则概率模型PCFG/Markov机器学习RNN/GAN实现难度低半天能跑通中需要理解概率图高调参训练生成可解释性强每条规则可追溯较强概率可拆解弱黑匣子命中率小数据集中高低欠拟合命中率大数据集低规则覆盖不够中高高答辩友好度高逻辑清晰高有数学支撑看你能不能讲清楚工程量小中大这张表的核心结论是数据集规模决定了你的上限可解释性决定了你的答辩下限。小数据集选机器学习是自找麻烦大数据集只用规则又浪费了数据。2.3 我的选型建议分场景给结论场景一数据集小于 10 万条时间紧。直接上 PCFG。PCFGProbabilistic Context-Free Grammar的核心思路是把口令拆成结构模板比如「字母段数字段特殊符号段」然后统计每段的概率分布。实现起来不复杂而且每一步都能解释。你可以在报告里画出模板概率树老师一看就懂。场景二数据集 10 万到 100 万条想拿高分。PCFG Markov 混合。PCFG 负责结构Markov 负责段内字符生成。这个组合的命中率比纯 PCFG 高 15%30%而且实现难度可控。场景三数据集超过 100 万条且你有 GPU 资源。可以上 RNNLSTM 或 GRU但必须做对比实验——用同样的测试集跑一遍 PCFG 基线再跑你的 RNN证明 ML 确实更好。没有对比实验的 ML 方案在答辩时基本等于没做。提示不管你选哪条路都要留出 20% 的数据做测试集而且测试集的口令不能出现在训练集里。我见过有人不小心把测试集混进训练集命中率虚高到 80%被老师当场拆穿。2.4 一个容易被忽略的评判点生成效率很多人只关注「命中率」忽略了「生成速度」。口令破解在实际场景里是有时间约束的——你不可能花三天生成一批候选口令。统计规则每秒能生成百万级候选PCFG 大概十万级RNN 生成速度取决于模型大小通常只有千级到万级。如果你的作业要求里提到了「在限定时间内命中尽可能多的口令」那生成效率就是一个硬指标。这时候纯 ML 方案反而吃亏因为它的推理速度慢。我一般会建议用 ML 生成候选池再用规则做优先级排序这样兼顾命中率和效率。3. PCFG 实战从零搭一个能跑的口令生成器PCFG 是我最推荐的入门路线因为它把「概率」和「结构」结合得恰到好处代码量不大但能讲的东西很多。这一章从数据预处理讲到生成每一步都给可复现的细节。3.1 口令结构拆解模板是怎么定义的PCFG 的第一步是定义模板。一个口令比如abc123!拆成L3D3S1其中 L 代表字母段、D 代表数字段、S 代表特殊符号段后面的数字是长度。password2024拆成L8D4。拆解规则我一般这么定import re def split_password(pwd): 将口令拆分为 字母段/数字段/特殊符号段 的序列 返回模板字符串和对应的段内容列表 # 按字符类型分组连续同类型字符合并为一段 segments [] current current_type None for ch in pwd: if ch.isalpha(): ch_type L elif ch.isdigit(): ch_type D else: ch_type S if ch_type current_type: current ch else: if current: segments.append((current_type, current)) current ch current_type ch_type if current: segments.append((current_type, current)) # 生成模板如 L3D3S1 template .join(f{t}{len(s)} for t, s in segments) return template, segments # 测试 print(split_password(abc123!)) # (L3D3S1, [(L,abc), (D,123), (S,!)]) print(split_password(password2024)) # (L8D4, [(L,password), (D,2024)])这段代码的关键在于连续同类型字符合并。有人会把abc拆成L1L1L1那样模板空间会爆炸概率统计也会失真。合并之后模板数量会大幅减少统计意义更强。拆解完之后你需要统计两样东西每个模板出现的频率结构概率以及每个段内具体内容的频率内容概率。比如L8D4这个模板出现了 5000 次其中L8段是password的有 800 次那P(password|L8) 800/5000。3.2 概率统计与模型训练几个必须处理的坑统计阶段有几个坑我一个个说。坑一低频模板怎么处理。有些模板只出现一两次如果直接保留生成时会产出大量奇怪结构。我一般设一个阈值出现次数少于 5 次的模板归入「其他」类或者直接丢弃。坑二段内容的大小写问题。Password和password要不要合并我的做法是统计时保留原始大小写但额外统计一个「首字母大写」的概率。生成时先按小写生成再按概率决定是否首字母大写。这样既保留了信息又避免了数据稀疏。坑三数字段的处理。数字段不是简单的「随机数字」很多人用生日、年份、手机号后四位。我一般会把数字段进一步分类纯年份19xx/20xx、纯生日月日组合、递增序列123/1234、重复数字111/666。分类之后分别统计概率生成时按类别采样。from collections import Counter, defaultdict class PCFGModel: def __init__(self): self.template_counter Counter() # 模板频率 self.segment_counter defaultdict(Counter) # 每个模板下各段的频率 self.total 0 def train(self, passwords): for pwd in passwords: template, segments split_password(pwd) self.template_counter[template] 1 self.total 1 for seg_type, seg_value in segments: # key 用 模板段类型段长度保证对齐 key f{template}|{seg_type}{len(seg_value)} self.segment_counter[key][seg_value] 1 def get_template_prob(self, template): return self.template_counter[template] / self.total def get_segment_prob(self, template, seg_type, seg_len, value): key f{template}|{seg_type}{seg_len} counter self.segment_counter.get(key) if not counter: return 1e-10 # 平滑处理避免零概率 total sum(counter.values()) return counter[value] / total这段代码里key的设计很关键。用「模板段类型段长度」做联合索引保证同一个模板下同类型同长度的段在一起统计。平滑处理用了一个极小的1e-10防止生成时出现零概率导致采样失败。3.3 口令生成与优先级排序训练完之后就是生成。生成逻辑是先按模板概率采样一个模板再按段内容概率逐段采样拼起来就是一个候选口令。import random def generate_password(model, num1000): 按 PCFG 概率生成候选口令 templates list(model.template_counter.keys()) template_weights [model.template_counter[t] for t in templates] results [] for _ in range(num): # 按概率选模板 template random.choices(templates, weightstemplate_weights, k1)[0] # 解析模板逐段生成 parts re.findall(r([LDS])(\d), template) pwd for seg_type, seg_len in parts: seg_len int(seg_len) key f{template}|{seg_type}{seg_len} counter model.segment_counter.get(key) if counter: values list(counter.keys()) weights list(counter.values()) pwd random.choices(values, weightsweights, k1)[0] else: # 没有统计数据的段用默认字符集随机填充 charset {L: abcdefghijklmnopqrstuvwxyz, D: 0123456789, S: !#$%^*}[seg_type] pwd .join(random.choices(charset, kseg_len)) results.append(pwd) return results生成之后优先级排序是提分的关键。同样生成 10 万个候选按概率从高到低排序先试概率高的命中率曲线会明显更陡。排序依据就是P(模板) × ∏P(段内容)取对数避免下溢。注意生成数量不是越多越好。我一般会生成 5 到 10 倍于测试集大小的候选量然后看命中率曲线。如果曲线在某个点之后基本平了说明模型覆盖能力到顶了再加量也没用。4. 机器学习路线RNN 生成口令的正确打开方式如果你数据集够大或者作业明确要求用机器学习那 RNN 是最稳妥的选择。但「能跑」和「能拿分」之间差了很多细节。4.1 数据预处理字符级还是 token 级口令生成的 RNN 通常是字符级建模因为口令长度短、字符集有限。字符集我一般控制在 95 个可打印 ASCII 字符以内超出范围的直接过滤。预处理步骤过滤掉长度小于 4 或大于 30 的口令太短没规律太长训练慢统一字符集非 ASCII 字符转成占位符或直接丢弃每条口令首尾加start和end标记按 8:1:1 划分训练/验证/测试集import numpy as np def build_vocab(passwords): 构建字符表 chars set() for pwd in passwords: chars.update(pwd) chars sorted(chars) # 特殊标记 vocab {pad: 0, start: 1, end: 2} for i, ch in enumerate(chars): vocab[ch] i 3 return vocab def encode(pwd, vocab, max_len30): 将口令编码为索引序列 seq [vocab[start]] [vocab.get(ch, 0) for ch in pwd] [vocab[end]] # 填充到固定长度 if len(seq) max_len: seq [vocab[pad]] * (max_len - len(seq)) else: seq seq[:max_len] return seq这里max_len的选择有讲究。设太小长口令被截断设太大填充太多影响训练效率。我一般取数据集口令长度的 95 分位数。4.2 模型结构与训练参数模型用两层 LSTM 加一个全连接输出层就够了不需要上 Transformer——口令生成的序列依赖没那么复杂Transformer 反而容易过拟合。import torch import torch.nn as nn class PasswordRNN(nn.Module): def __init__(self, vocab_size, embed_dim64, hidden_dim256, num_layers2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm nn.LSTM(embed_dim, hidden_dim, num_layers, batch_firstTrue, dropout0.3) self.fc nn.Linear(hidden_dim, vocab_size) def forward(self, x, hiddenNone): emb self.embedding(x) out, hidden self.lstm(emb, hidden) logits self.fc(out) return logits, hidden训练参数batch_size 128学习率 1e-3优化器 Adam训练 20 到 30 个 epoch。关键点在于用验证集的 loss 做早停不要死磕训练轮数。我见过有人训练到 loss 0.01结果生成的口令全是训练集原样这就是过拟合的典型表现。4.3 生成策略温度参数怎么调RNN 生成时温度参数temperature直接决定生成口令的多样性。温度低0.5生成的口令保守、接近训练集高频模式温度高1.5多样性好但容易生成乱码。我的做法是生成多组不同温度的结果分别评估命中率选最优温度。通常 0.7 到 1.0 之间效果最好。def generate_with_temperature(model, vocab, start_token, temperature0.8, max_len30): 按温度采样生成口令 model.eval() idx2char {v: k for k, v in vocab.items()} input_seq torch.tensor([[vocab[start_token]]]) hidden None result [] with torch.no_grad(): for _ in range(max_len): logits, hidden model(input_seq, hidden) logits logits[:, -1, :] / temperature probs torch.softmax(logits, dim-1) next_idx torch.multinomial(probs, 1).item() if next_idx vocab[end]: break if next_idx 2: # 跳过特殊标记 result.append(idx2char[next_idx]) input_seq torch.tensor([[next_idx]]) return .join(result)温度参数的本质是调整概率分布的平滑程度。温度趋近 0 时等价于贪心搜索温度趋近无穷时等价于均匀采样。实际用的时候我一般会跑 0.6、0.8、1.0 三组每组生成 5 万条看哪组的命中率最高。5. 避坑指南那些年我在口令破解作业里翻过的车这一章全是血泪经验每一条都是真实踩过的坑。如果你正在做这个作业建议逐条对照检查。5.1 命中率虚高测试集泄露现象模型报告命中率 75%答辩时老师让现场跑一遍结果只有 30%。原因训练集和测试集有重叠。最常见的情况是数据集本身有大量重复口令去重没做干净同一条口令既在训练集又在测试集。解决划分数据集之前先做全局去重。用set()去重后再划分。另外测试集的口令不能以任何形式出现在训练过程中包括验证集调参时也不能偷看测试集。5.2 生成的口令全是训练集原样现象RNN 生成 10 万条口令去重后只剩 2 万条而且大部分能在训练集里找到一模一样的。原因过拟合。模型记住了训练样本没有学到泛化规律。温度参数太低也会加剧这个问题。解决加 dropout0.2 到 0.4用早停提高温度参数。另外可以在生成时加一点随机扰动比如随机替换一个字符。判断是否过拟合的简单方法看生成口令与训练集的重合率超过 50% 就是过拟合了。5.3 PCFG 模板爆炸现象统计出几万个模板每个模板只有一两条口令生成时完全随机命中率极低。原因模板定义太细。比如把abc123拆成L1L1L1D1D1D1而不是L3D3。解决连续同类型字符合并低频模板归并。我一般会把出现次数少于 10 次的模板合并成「其他」类或者直接丢弃。模板总数控制在 500 到 2000 之间比较合理。5.4 数字段处理太粗糙现象生成的口令里数字段全是随机数字但测试集里的数字大量是年份和生日导致命中率上不去。原因数字段没有做子分类统一按字符频率采样了。解决把数字段分成年份、生日、序列、重复、随机五类分别统计概率。生成时先选类别再按类别生成。这个改动通常能提升 10% 以上的命中率。5.5 评估指标选错现象用准确率accuracy评估模型报告里写「准确率 95%」但实际命中率很低。原因口令生成不是分类问题准确率没有意义。正确的指标是「命中率」——生成的候选口令中有多少比例出现在测试集里。解决评估时用命中率曲线hit rate curve横轴是生成数量纵轴是命中比例。另外可以报告「每命中一条口令需要尝试多少次」这个指标更直观。6. 最后说一个提分技巧混合策略的优先级排序如果你时间充裕我强烈建议做混合策略用 PCFG 生成候选池用 RNN 补充长尾最后按概率排序。具体做法是PCFG 生成 5 万条高概率候选RNN 生成 5 万条多样性候选合并去重按 PCFG 概率打分排序取前 1 万条作为最终输出这个策略的好处是兼顾了 PCFG 的可解释性和 RNN 的覆盖能力。在报告里你可以分别展示三条曲线纯 PCFG、纯 RNN、混合策略然后说明混合策略在相同尝试次数下命中率最高。这种对比实验是答辩时最加分的部分。def hybrid_generate(pcfg_model, rnn_model, vocab, num_pcfg50000, num_rnn50000): 混合生成PCFG 候选 RNN 候选按概率排序 # PCFG 生成 pcfg_candidates generate_password(pcfg_model, numnum_pcfg) # RNN 生成 rnn_candidates [] for _ in range(num_rnn): pwd generate_with_temperature(rnn_model, vocab, start, temperature0.8) if len(pwd) 4: rnn_candidates.append(pwd) # 合并去重 all_candidates list(set(pcfg_candidates rnn_candidates)) # 按 PCFG 概率打分排序RNN 候选如果没有对应模板给一个基础分 def score(pwd): template, segments split_password(pwd) p pcfg_model.get_template_prob(template) for seg_type, seg_value in segments: p * pcfg_model.get_segment_prob(template, seg_type, len(seg_value), seg_value) return p all_candidates.sort(keyscore, reverseTrue) return all_candidates这段代码的核心是score函数——它给每条候选口令打一个概率分然后按分排序。RNN 生成的候选如果不符合任何已知模板概率分会很低自然排在后面。这样既保留了 RNN 的多样性又保证了高概率候选优先被尝试。回过头看这个作业最大的价值不是让你学会某个具体算法而是让你理解「在资源受限的情况下怎么用概率思维做优先级决策」。口令破解本质上是一个搜索问题你的模型就是你的搜索策略。策略好不好不看它多复杂看它在有限尝试次数内能命中多少。从那以后我每次做类似的数据驱动任务都会先问自己三个问题数据量够不够支撑复杂模型可解释性重不重要评估指标选对了吗这三个问题想清楚路线基本就不会选错。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑