【硬核实战】从 Python 单线程到 GPU 满载:记一次 PDF 密码的暴力破解与性能优化
前言:
这几天翻出了一个重要的加密 PDF 文件,密码忘了,但脑海中隐约记得密码的规律:“一个大写字母打头,后面跟着5位数字”。
总搜索空间很好算:26个字母 $\times$ $10^5$ 种数字组合 = $2,600,000$ 次(260万次)。
260 万次并不多,我的第一反应是写个 Python 脚本硬算。但实操后发现,由于对底层计算体系架构的忽视,我绕了一个巨大的弯路。这篇文章将记录我是如何从一段“画蛇添足”的冗长代码,一步步转移到榨干 GPU 算力的工业级爆破方案。
一、 反面教材:被 I/O 拖垮的 Python 脚本
最开始,我基于 pikepdf 库写了下面这段代码,试图用 Python 直接遍历破解:
Python
import pikepdf
pdf_path = r"C:\Users\JinRC\Downloads\eSim (1).pdf"
for num in range(100000):
password = f"D{num:05d}"
try:
pdf = pikepdf.open(pdf_path, password=password)
print("密码找到:", password)
pdf.close()
break
except:
pass
1.1 架构级缺陷分析
从计算机组成原理和操作系统的视角来看,这段代码存在极大的资源浪费:
- 单线程阻塞(GIL限制): Python 的
for循环是单线程串行执行的。密码哈希碰撞是典型的 CPU 密集型任务,而这段代码完全没有利用到现代处理器的多核并发能力。 - 灾难性的磁盘 I/O: 每次调用
pikepdf.open(),程序不仅在做哈希校验,还在请求操作系统分配文件句柄、进行磁盘 I/O 读取、解析 PDF 头部和交叉引用表(XREF)。 - 结论: 将“计算密集型的哈希匹配”与“I/O 密集型的文件解析”强行耦合,导致 CPU 大量时钟周期全部浪费在等待内存和硬盘调度上。对于 260 万次的遍历,这种跑法可能需要挂机数天。
二、 核心重构:提取哈希与计算分离
正确的破译逻辑,必须将特征提取与碰撞计算彻底分离。用 CPU 剥离文件外壳,用 GPU 处理高并发的纯粹数学运算。
2.1 提取 PDF 哈希值
通过开源的 pdf2john.py 脚本,我们不需要再读取原始 PDF 文件,而是直接提取出它的密码加密特征(Hash)。
DOS
python pdf2john.py "eSim (1).pdf" > raw_hash.txt
2.2 数据清洗
提取出的内容会带有文件名,例如 eSim (1).pdf:$pdf$5*6*256*...。
专业的运算工具无法识别带有文件名的格式,必须手动删掉冒号及之前的所有内容,只保留 $pdf$ 开头的纯净 ASCII 字符串,另存为 hash.txt。
💡 避坑小贴士:
文本清洗必须做到“一字不差”,文件末尾绝不能有任何空行、回车或中文句号。否则后续加载时会直接报错
Token length exception。
三、 显卡出战:使用 Hashcat 掩码攻击
这里我们请出密码学爆破界的核武器 —— Hashcat。它能直接调度显卡的计算核心(如 AMD 的 MCU 或 NVIDIA 的 CUDA)执行并发计算。
3.1 确定哈希协议模式 (Hash Mode)
观察我们提取出的哈希:$pdf$5*6*256*...
5代表加密版本号 (V=5)6代表加密修订版本 (R=6)256代表采用了 256-bit AES 加密
在 PDF 规范中,R=6 对应的是 PDF 1.7 Extension Level 8,内部嵌套了多轮 SHA-256 迭代,属于典型的“慢哈希”。在 Hashcat 的模块映射字典中,它严格对应的参数是 -m 10700。(如果错写成 10500,程序会直接闪退拒绝执行)。
3.2 第一次执行:被忽视的寄存器浪费
基于我的记忆前提(1位大写字母+5位数字),构造掩码规则 ?u?d?d?d?d?d,开始执行:
DOS
hashcat.exe -m 10700 -a 3 hash.txt ?u?d?d?d?d?d -w 3
运行结果: 算力拉跨。
两组 AMD Radeon Graphics 并发,哈希率仅有可怜的 541 H/s。系统预估跑完这 260 万次需要 1 小时 18 分钟。
缺陷指出:
日志中抛出了一行警告:ATTENTION! Pure (unoptimized) backend kernels selected.
程序默认加载了“纯内核”,它为了兼容长达 127 字节的超长密码,强行给每个线程分配了大量的 GPU 寄存器空间。而我们的密码明明只有 6 位,大量的寄存器资源被死死占满却处于空载状态,导致并发线程数断崖式下跌。
3.3 第二次执行:开启内核优化(性能飙升 8 倍)
立即按下 q 中止任务,在命令末尾追加大写的 -O 参数(Optimized Kernel),强制将密码最大长度限制在 16 字节以内,释放冗余寄存器。
DOS
hashcat.exe -m 10700 -a 3 hash.txt ?u?d?d?d?d?d -w 3 -O
| 状态 | 内核模式 | 显卡占用率 | 算力速率 | 预计总耗时 | |||||
|---|---|---|---|---|---|---|---|---|---|
| 优化前 | Pure Kernel (0-127 bytes) | - | 541 H/s | 1 小时 18 分 | |||||
| 优化后 | Optimized Kernel (0-16 bytes) | 97% | 4712 H/s | 9 分 11 秒 |
此时底层硬件调度彻底释放,速率飙升至 4712 H/s,性能直接翻了近 8.7 倍。
四、 成果与总结
在满载运行仅仅 3 分 51 秒后(遍历进度到达 32.57% 时),终端输出绿字:
Status.........: Cracked
最后一行成功打印出了明文密码:J97421。
技术复盘:
- 不要用高级 API 解决底层计算问题: 单线程 Python 调库做文件级爆破是极其业余的,必须将哈希提取与 GPU 并发运算解耦。
- 硬件性能取决于资源分配: 面对“短掩码”时,
-O优化参数是榨干 GPU 并发性能的决定性开关。省下寄存器,就是成倍扩充并发线程池。
专业的事情,一定要交给专业的工具去做。
评论区