口令破解课程作业:合成靶场与机器学习预测口令脆弱性

发布时间:2026/10/29 2:32:14
口令破解课程作业:合成靶场与机器学习预测口令脆弱性 简介面向高校网络空间安全课程学习者与选修国科大 Web 安全技术的学生这份口令破解方向的大作业资料围绕密码特征提取、特征统计与暴力猜测三条主线展开可作为课程实验参考与机器学习结合安全分析的练习样本。压缩包共 155 个文件约 170.54MB以 txt 文本、csv 数据表、py 脚本为主另含 xlsx 统计表、png 结果图与 sample 样本文件兼顾数据处理、特征工程与实验记录。已有 369 人学习下载。读者可从中获取口令特征构造与统计思路、字典生成与猜测流程的参考代码、针对 csdn/yahoo 等数据集的训练与结构分析样例以及结果图表和辅助文件便于对照复现实验、理解从原始密码到特征再到猜测评估的完整链路适合具备 Python 与基础安全知识的学习者按模块查阅。1. 口令破解不是拿字典碰运气这份课程作业资源到底解决了什么问题某高校 web 安全技术 课的期末作业题目叫 口令破解很多人第一反应是找个字典工具跑一晚上看谁先出结果。真按这个思路做交上去的报告基本只有一张截图和一句跑出来了分数不会高因为整件事里最值钱的部分——命中率怎么定义、搜索空间怎么估、哪些口令会被优先攻破——全被跳过了。这份课程作业资源包把这三件事拆开做了先用离线靶场造一批可控哈希再用字典、规则、掩码三种模式跑对照实验最后训练一个轻量 机器学习 模型在真正跑破解之前就预测出哪些口令最脆弱。适合正在做安全课程设计的学生也适合想把口令强度审计流程固化成脚本的运维和安全岗从业者。2. 靶场搭建为什么必须用合成数据而不是现成词表2.1 依赖清单哪些是必需哪些是锦上添花这套实验的依赖分成两层。底层是哈希计算和破解引擎上层是数据分析和建模。我的建议是先把底层跑通别一上来就 pip install 一堆东西最后环境炸了都不知道是哪一层的问题。# 底层破解引擎按自己的发行版选包管理器这里用通用写法 # hashcat 提供 GPU/CPU 多后端是三种攻击模式的主力 hashcat --version # 如果没有独显确认 OpenCL 运行时是否装好否则会报 no devices found hashcat -I # 上层数据分析与建模 python3 -m venv venv source venv/bin/activate pip install pandas scikit-learn numpy matplotlib joblibhashcat -I这条命令很关键它列出所有可用的计算设备。如果输出里只有 CPU 一个设备说明 OpenCL 运行时没装或者显卡驱动不匹配后面所有跑分都会慢一个数量级。真跑不动也别硬扛加-D 1强制走 CPU 后端小规模靶场足够用只是别拿它去估真实环境的破解时长。Python 这边我只留了五个包。pandas处理结果表scikit-learn做模型joblib存模型文件matplotlib画命中率曲线。有人会想上 XGBoost 或者深度学习框架说实话在这个数据规模下属于杀鸡用牛刀几百上千条样本用随机森林就够训练时间从分钟级降到秒级调试体验完全不同。2.2 用合成数据造靶场模板化生成 可控分布靶场数据的来源是整份作业里最容易翻车的地方。直接从网上下载所谓真实泄露口令库一是合规上有疑问二是你根本不知道里面混了什么三是没法控制分布后面做机器学习的时候连分层抽样都做不了。我的做法是模板化生成先定义若干口令构造模板再在模板上做随机扰动这样每一条数据的生成来源都是已知的可以当分组切分的依据。import random, hashlib, string, csv random.seed(20240501) # 固定随机种子保证任何人复现都是同一批数据 # 五类口令构造模板覆盖从弱到强的典型分布 TEMPLATES { pure_digit: lambda: .join(random.choices(string.digits, krandom.randint(4, 8))), lower_word: lambda: .join(random.choices(string.ascii_lowercase, krandom.randint(4, 8))), word_year: lambda: .join(random.choices(string.ascii_lowercase, krandom.randint(3, 6))) str(random.randint(1970, 2024)), leet_suffix: lambda: .join(random.choices(string.ascii_lowercase, krandom.randint(3, 6))) random.choice([, !, #]) str(random.randint(0, 99)), mixed_strong: lambda: .join(random.choices( string.ascii_letters string.digits !#$%, krandom.randint(10, 14))), } def make_ntlm(plain: str) - str: # NTLM 是 UTF-16LE 编码后取 MD4直接用 md5 会全部对不上 return hashlib.new(md4, plain.encode(utf-16-le)).hexdigest() rows [] for i in range(1200): tpl random.choices(list(TEMPLATES), weights[2, 3, 3, 2, 1])[0] plain TEMPLATES[tpl]() rows.append({id: i, plain: plain, hash: make_ntlm(plain), alg: ntlm, template: tpl}) with open(lab_targets.csv, w, newline) as f: csv.DictWriter(f, fieldnamesrows[0].keys()).writeheader() csv.DictWriter(f, fieldnamesrows[0].keys()).writerows(rows)这段脚本有三个地方值得单独说。第一是random.seed()固定种子之后任何人跑出来的 1200 条数据完全一致答辩的时候别人能当场复现你的命中率这比任何文字描述都有说服力。第二是make_ntlm很多人第一次写 NTLM 哈希会直接套 MD5结果 hashcat 一条都命不中原因是 NTLM 的算法是明文转 UTF-16LE 再取 MD4编码和摘要算法都和 MD5 不同。第三是权重参数weights[2,3,3,2,1]它决定了弱口令和强口令的比例默认让中等难度占多数这样三种攻击模式的差异才能体现出来如果全是纯数字字典攻击一秒全中后面的对照实验就没意义了。生成的lab_targets.csv里同时有明文和哈希。明文这一列只在本地靶场用做特征分析的时候需要它来算标签但交作业的时候要单独说明明文列不参与模型推理这句话写在报告里能省掉答辩时一半的追问。2.3 字段设计与哈希格式对齐数据表设计看着琐碎但它决定了后面所有统计口径能不能对上。我给每个字段定死用途字段类型用途是否进模型idint唯一标识串联破解结果否plainstr靶场明文用于算标签和特征否仅离线标注hashstr交给破解引擎的目标否algstr哈希算法标识对齐 hashcat 的-m否templatestr生成模板做分组切分作为分组键crack_timefloat实际破解耗时后处理写入否alg这个字段是给 hashcat 的-m参数用的。教学靶场用 NTLM 对应-m 1000换成 MD5 就是-m 0SHA1 是-m 100。算法一旦选错引擎不会报错它只会安静地一条都不命中然后你会花两小时怀疑自己的字典有问题。template这一列在建模阶段至关重要第 4 章会展开讲——如果按行随机切分训练测试集同一模板生成的样本会同时出现在两边模型只需要记住模板特征就能拿到接近 100% 的准确率这个分数毫无意义。再补一个容易忽略的细节明文里如果出现中文或特殊符号哈希计算前必须统一编码。我一般强制全部走 UTF-8 转 UTF-16LE 的路径并且在数据生成脚本里加一行断言检查生成的口令是否只包含可打印 ASCII避免后面因为编码问题出现明明口令对但哈希不匹配的灵异现象。3. 三种攻击模式的取舍字典、规则、掩码怎么选3.1 字典攻击命中率的分子分母要先定义清楚字典攻击是最容易理解也最容易被做烂的一种。跑一条hashcat -a 0谁都会但报告里那句字典攻击命中率 62%如果不写清楚分母是什么这个数字就是空的。分母有三个候选全部靶场口令数、字典覆盖的口令数、以及按长度分桶后的子集数。我一般三个都算因为它们的结论完全相反——全量口径下 62% 看着还行但如果字典本身只覆盖了 70% 的靶场口令那在字典能覆盖的范围内命中率其实接近 89%说明字典质量不错短板在覆盖面。# -a 0 字典模式-m 1000 是 NTLM--potfile-disable 防止上一轮结果污染本轮 hashcat -a 0 -m 1000 lab.hash wordlist.txt \ --potfile-disable \ --outfile result_dict.txt --outfile-format 2 \ --status --status-timer 5参数逐个解释-a 0指定字典模式-m 1000是哈希类型lab.hash是从 CSV 里抽出来的纯哈希列一行一个。--potfile-disable是我强烈建议加上的hashcat 默认会把已破解的结果写进 potfile第二次跑同样的目标时它会直接跳过导致你以为命中率突然变成 0——这个坑在第 5 章还会细说。--outfile-format 2表示输出哈希:明文格式方便后面用 pandas 直接按:切开。字典本身我建议自己构造而不是找现成的。构造方式是从靶场的生成模板反推高频片段比如年份段 1970-2024、常见后缀!#加两位数、以及字母段的 n-gram 组合。这样构造出来的字典和目标分布是匹配的但注意不要直接把靶场明文塞进去——那就成了开卷考试命中率 100% 也证明不了任何事。我一般的做法是用模板 A 生成靶场用模板 A 的参数空间构造字典但不使用任何一条靶场明文这样字典和目标同分布但不同个体实验才成立。3.2 规则与掩码把搜索空间换算成时间规则攻击本质是字典的膨胀。best64.rule这类规则文件会对字典里每个词做大小写变换、末尾追加数字、字符替换a→o→0一条词能变出几十条候选。好处是不需要额外准备素材坏处是搜索空间成倍膨胀跑分不变的情况下总耗时直接乘以规则数。# -a 0 -r 规则文件字典膨胀每条词派生多条候选 hashcat -a 0 -m 1000 lab.hash wordlist.txt \ -r rules/best64.rule --potfile-disable \ --outfile result_rule.txt --outfile-format 2 # -a 3 掩码模式直接枚举字符集组合 # ?l 小写 ?u 大写 ?d 数字 ?s 特殊字符 hashcat -a 3 -m 1000 lab.hash ?l?l?l?l?d?d \ --potfile-disable --outfile result_mask.txt --outfile-format 2掩码模式最容易让人上头。?l?l?l?l?d?d看起来只有 6 位但空间是 26⁴ × 10² ≈ 4.57 亿条。这时候必须先用hashcat -b -m 1000跑一次基准测试拿到当前设备每秒能算多少次再用空间除以速率估时间。如果算出来要跑十几个小时那就该缩小掩码范围或者接受掩码攻击只覆盖某一段长度区间这个限制并在报告里写清楚。三种模式的核心参数我整理成一张表选型的时候照着看就行模式参数适用场景空间量级主要风险字典-a 0有高质量词表词表行数覆盖面不足字典规则-a 0 -r词表质量中等词表 × 规则数时间线性膨胀掩码-a 3已知长度与字符集规律字符集^长度指数爆炸混合-a 6词固定后缀词表 × 后缀空间参数写错就白跑实际跑的时候我一般按字典 → 字典规则 → 混合 → 掩码的顺序递进每一步只对前一步没命中的剩余目标跑。这样总耗时最低而且报告里能画出一条很漂亮的累积命中率曲线说明每一层策略各自的增量贡献。这比单跑一种模式然后说命中率 60%要有信息量得多。3.3 结果落库与统计口径破解跑完只是原料真正要交的是分桶统计。我一般把 hashcat 的输出文件读进 pandas按长度、字符类别数、是否含年份、生成模板四个维度分桶算出每桶的命中率。import pandas as pd targets pd.read_csv(lab_targets.csv) cracked pd.read_csv(result_all.txt, sep:, headerNone, names[hash, plain]) # 用哈希做左连接命中的打上标记 df targets.merge(cracked, onhash, howleft, suffixes(, _cracked)) df[cracked] df[plain_cracked].notna().astype(int) # 派生统计维度 df[length] df[plain].str.len() df[class_cnt] df[plain].apply( lambda s: sum([any(c.islower() for c in s), any(c.isupper() for c in s), any(c.isdigit() for c in s), any(not c.isalnum() for c in s)]) ) report df.groupby(length).agg(total(id, count), hit(cracked, sum)) report[hit_rate] (report[hit] / report[total]).round(3) print(report)这段代码的关键是merge用哈希而不是 id 做连接键。因为破解引擎的输出里只有哈希没有你的业务 id用 id 连接必然对不上。class_cnt这个派生特征算的是口令里出现了几类字符小写、大写、数字、符号值域 1 到 4它和命中率的相关性通常比长度还强第 4 章会直接拿它当模型特征。统计口径上有个细节要提前定死同一轮实验中一个目标只算一次命中不管它被字典还是规则先命中。如果不去重规则攻击会把字典已经破出来的目标再算一遍导致总命中数超过靶场总数这种荒谬结论。我在 merge 前会对cracked做drop_duplicates(subsethash)这行代码不起眼但它能让你的报告数字自洽。4. 机器学习建模把哪些口令先倒变成可预测的分数4.1 特征工程不碰明文只用可观测的结构信息建模这一步最容易走偏的地方是特征选得太富。有人会把明文直接扔进字符级 n-gram 做 TF-IDF效果确实好但这就等于模型见过答案实际部署时你拿不到明文——线上只能拿到用户设置口令时的输入流或者已有哈希。所以我的原则是特征只能来自明文的结构属性不能来自明文字符本身的可逆信息。具体抽这几类import re import numpy as np import pandas as pd KEYBOARD_ROWS [qwertyuiop, asdfghjkl, zxcvbnm, 1234567890] def has_keyboard_run(s: str, min_len: int 3) - int: # 检测是否存在键盘上连续相邻的片段如 qwe / asd / 789 low s.lower() for row in KEYBOARD_ROWS: for i in range(len(row) - min_len 1): if row[i:i min_len] in low: return 1 return 0 def extract_features(plain: str) - dict: n len(plain) lower any(c.islower() for c in plain) upper any(c.isupper() for c in plain) digit any(c.isdigit() for c in plain) symbol any(not c.isalnum() for c in plain) return { length: n, class_cnt: sum([lower, upper, digit, symbol]), digit_ratio: sum(c.isdigit() for c in plain) / n, has_year: int(bool(re.search(r(19|20)\d{2}, plain))), has_kbd_run: has_keyboard_run(plain), repeat_ratio: 1 - len(set(plain)) / n, # 字符重复度 is_pure_digit: int(plain.isdigit()), tail_digit_cnt: len(re.search(r\d*$, plain).group()), # 末尾连数字长度 } feat pd.DataFrame([extract_features(p) for p in df[plain]])八个特征每一个都有明确的安全解释。has_year抓的是生日/年份后缀这种最常见的弱口令模式has_kbd_run抓键盘滑动tail_digit_cnt抓末尾补数字的习惯repeat_ratio抓111111、aaaaaa这类。它们共同的优点是在不知道明文的情况下这些属性也能从口令策略日志或者用户输入行为里近似推断出来模型才有落地的可能。标签怎么来这是这套设计里我最满意的一点。标签直接用第 3 章的破解结果——被字典或规则在限定时间内破出来的记 1没破出来的记 0。这样标签不是人拍的而是有客观依据的实际脆弱性。代价是标签和攻击策略强相关换一套字典标签就会变。这一点必须在报告里写清楚否则别人复现时数字对不上会以为你造假。4.2 训练与评估准确率在这里是会骗人的数据分布是典型的不平衡样本弱口令占多数。如果直接看准确率一个全部预测为脆弱的模型也能拿到 65% 以上的分数但这个模型毫无用处。所以评估指标我固定用三个AUC、PR-AUC、以及 Top-K 召回率。前两个是常规操作第三个才是真正贴合业务的——安全团队不可能对所有口令都强制重置只能处理风险分数最高的一批那 Top-K 召回率回答的就是如果我只看分数最高的 30%能覆盖多少真正脆弱的口令。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GroupKFold from sklearn.metrics import roc_auc_score, average_precision_score X, y, groups feat.values, df[cracked].values, df[template].values gkf GroupKFold(n_splits5) scores [] for tr, te in gkf.split(X, y, groups): clf RandomForestClassifier(n_estimators300, max_depth8, class_weightbalanced, random_state42) clf.fit(X[tr], y[tr]) prob clf.predict_proba(X[te])[:, 1] # Top-30% 召回按预测分数排序取前 30% 的样本看覆盖多少真实命中 k int(len(prob) * 0.3) topk_recall y[te][np.argsort(prob)[-k:]].sum() / max(y[te].sum(), 1) scores.append([roc_auc_score(y[te], prob), average_precision_score(y[te], prob), topk_recall]) print(pd.DataFrame(scores, columns[AUC, PR-AUC, Top30%Recall]).mean().round(3))GroupKFold配合groupstemplate是这段代码的核心。用普通 KFold 的话同一模板生成的样本会跨训练测试集泄漏AUC 会虚高到 0.98 以上看着很爽一换数据就崩。按模板分组之后模型必须在没见过某种构造方式的前提下做预测AUC 通常会掉到 0.80 到 0.88 之间——这个掉下来的过程恰好就是报告里最有价值的一段分析。class_weightbalanced用来对抗样本不平衡max_depth8是防过拟合的手动刹车。树模型在这个特征维度下不需要调太多参我试过把深度放到 20训练集 AUC 逼近 1.0测试集反而降了两个点典型的过拟合信号。4.3 模型输出接进审计流水线模型的最终产物不是一个分数而是一张排好序的处理清单。落地时我会把预测概率按 0.7 / 0.4 两个阈值切成三档高风险直接建议强制重置中风险要求下次登录时修改低风险只做记录。这套分级比全部强制改密码现实得多也比一律不管有用得多。clf_full RandomForestClassifier(n_estimators300, max_depth8, class_weightbalanced, random_state42) clf_full.fit(X, y) df[risk_score] clf_full.predict_proba(X)[:, 1].round(4) df[action] pd.cut(df[risk_score], bins[-0.01, 0.4, 0.7, 1.0], labels[record_only, force_on_next_login, force_reset]) df.sort_values(risk_score, ascendingFalse)[ [id, risk_score, action, template]].to_csv(audit_plan.csv, indexFalse)注意这里用全量数据重新训练了一次模型再出清单而评估阶段用的是交叉验证的结果。这两件事不能混评估要严格隔离出清单可以用全量数据。有人把评估阶段的折外预测直接拿去当最终分数样本外和样本内的分数混在一张表里排序就失真了。pd.cut的两个阈值不是随便定的一般按业务能承受的重置量倒推如果强制重置会影响 15% 的用户那就把 0.7 这个阈值调到让高风险档正好占 15%。这个反推过程写进报告比单纯堆模型指标更能体现工程思维。5. 避坑与排查五个真实翻车记录5.1 环境和数据层的坑现象hashcat 启动直接报No devices found或者识别到设备但速度只有几百 H/s。原因通常有两个一是 OpenCL 运行时没装引擎找不到任何计算后端二是装了但显卡驱动版本和运行时对不上设备被识别成 fallback CPU。解决方式是先跑hashcat -I看设备列表如果只有 CPU先补运行时如果连 CPU 都没有那是安装包不完整。实在修不好驱动加-D 1强制 CPU 后端小靶场完全够用别在环境上耗一整天。现象字典攻击第一轮命中 62%第二轮同样的命令跑出来是 0%。这是 potfile 干的。hashcat 默认把已破解结果写进~/.hashcat/hashcat.potfile下次遇到相同哈希直接跳过不输出。做多轮对照实验时这个机制会让后面每一轮的命中率都被前一轮吃掉。解决办法是每次实验都加--potfile-disable或者每轮开始前手动清理 potfile。我现在的习惯是把清 potfile写进实验脚本第一行血泪经验。现象手动用 Python 算了哈希去比对明明明文一致引擎就是不命中。九成是编码问题。NTLM 要求明文先转 UTF-16LE 再取 MD4如果直接对 UTF-8 字节做摘要哈希值完全不同。另一个变种是往靶场里塞了带全角字符或空格的口令生成和解析两端编码不一致。排查方法是拿一条已知口令用两种方式各算一次哈希肉眼比对前 8 位不一样就说明编码路径错了。5.2 算法和结果解读层的坑现象模型 AUC 跑到 0.99换一批数据掉到 0.6。典型的切分泄漏。同一模板生成的样本在特征空间里高度相似随机切分会让训练集和测试集包含同源样本模型只需记住模板特征就能满分。解决办法是用GroupKFold按模板分组切分让测试集里的构造方式在训练时完全没出现过。改完之后 AUC 会明显下降但那个数字才是真的。现象掩码攻击跑了一整晚还没结束日志里进度条几乎不动。掩码长度和字符集一起膨胀空间是指数级的。?l?l?l?l?l?l?d?d看着只有 8 位实际是 26⁶ × 100 ≈ 3×10¹⁰ 条。开工前必须先hashcat -b -m 算法拿速率用空间除以速率估时间超过可接受范围就砍字符集或者缩长度。另外慢哈希比如成本因子较高的 bcrypt速率会低好几个数量级这类目标本来就不该用掩码硬啃报告里应该转而讨论为什么慢哈希能有效抬高攻击成本这才是加分项。现象统计出来的总命中数比靶场总数还多。多轮攻击的结果文件被直接拼接同一个目标被字典和规则各算了一次命中。修复方式是在合并前按哈希去重cracked.drop_duplicates(subsethash)。这个错误极其常见而且从最终数字上很容易看出来——命中率超过 100% 的报表一眼就知道口径没统一。5.3 复现和协作层的坑现象别人照你的脚本跑结果每次都不一样。随机种子没固定或者固定了但只固定了 Python 的random没固定 numpy 的。数据生成、样本切分、模型初始化三处都要设 seed缺一处就会漂。我在脚本开头统一写一个set_all_seeds(42)函数三行代码解决后续所有扯皮。现象报告里的命中率和代码跑出来的对不上差几个百分点。多半是靶场数据版本不一致。合成数据脚本改过一版但没重新生成 CSV或者生成后手动编辑过几行。解决办法是把数据生成脚本和 CSV 一起纳入版本管理报告里标注数据文件的哈希值别人复现时先校验文件指纹再跑。6. 复现验证与进阶技巧用一份自测清单卡住交付质量这套实验做完最容易出现的状态是能跑通但说不清楚。我在交付前会强制走一遍自测清单五项全过才算完成检查项判定标准不过怎么办数据可复现同一种子重跑CSV 哈希值一致检查所有随机源攻击分层清晰每轮只对剩余目标跑拆分结果文件统计口径自洽各桶命中数之和等于总命中数按哈希去重切分无泄漏训练测试集模板不重叠换 GroupKFold结论可落地有分档处置建议补阈值反推清单之外有一个技巧我觉得比模型本身更值得写进报告用累积命中率曲线代替单点命中率。横轴是攻击轮次字典、字典规则、混合、掩码纵轴是累积命中率画出来是一条单调上升但边际递减的曲线。这条曲线同时回答了两个问题——哪种策略性价比最高看斜率以及继续投入还有多少收益看尾部是否趋平。如果第三轮之后曲线基本躺平说明剩下的目标已经超出这套攻击的覆盖范围再堆算力也是白烧。另一个技巧是给模型加一个解释层。随机森林自带feature_importances_把八个特征的重要性排序打出来通常length、class_cnt、has_year排前三这和常识完全吻合说明模型学到了真东西而不是噪声。如果有人问你的模型凭什么说这条口令弱直接甩这张图比任何口头解释都硬。最后一个习惯每做完一轮实验先把结果文件归档到带时间戳的目录再改脚本。我吃过一次亏脚本改了三版之后想回头对比第一版的结果发现输出文件被覆盖了只能重跑。从那以后我每次跑实验前都强制走一遍建目录、定版本、留原始日志这三步哪怕只是改一个阈值也不省。这套流程看着笨但它保证了报告里每一个数字都能追溯到某一次具体的运行。希望这些拆解能帮到你把这份作业从跑出来了做成说得清楚、别人还能复现。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询