简介:一套用于破解Excel打开密码(纯数字)的Python源码,面向Excel使用频繁、因密码遗忘或需批量解除纯数字打开密码而困扰的办公人员、运维人员以及Python学习者,适用于日常办公中的紧急解锁与自动化测试场景。压缩包共2个文件:1个可直接运行的.py脚本和1个.mp4操作演示视频;脚本封装了针对纯数字密码的自动尝试逻辑,视频则展示配置运行环境与执行破解的完整过程,便于对照验证。包体大小14.55MB,轻量紧凑,下载后即可基于Python环境运行。源码既提供可应对常见纯数字密码场景的现成脚本,也通过录屏讲解帮助读者理解密码组合生成、文档读写与循环尝试等实现要点;对于希望掌握Excel自动化处理或开发密码恢复工具的开发者,是很好的参考样本。已有1856人学习下载,适合需要快速解决Excel密码问题的技术用户,也是Python办公自动化初学者的练手项目。
1. 纯数字 Excel 打开密码:一个离线可穷举的密码破解问题
Excel 的"打开密码"和多数人想的不一样:它不是让 Excel 弹一个输入框来验证,而是把整个 xlsx 文件加密之后再落盘。密码对不对,本地就能离线判断,根本不需要真正打开 Excel。于是问题就只剩一个——候选空间有多大。纯数字 6 位只有 100 万个组合,8 核 CPU 上按小时算;8 位就是 1 亿个组合,按天算。网上这类"Py破解Excel打开密码(纯数字).rar"分享包,解压后通常是一个 Python 脚本加说明,核心就是三件事:用 msoffcrypto 校验候选密码、用 itertools 生成数字序列、用 multiprocessing 并行加速。那层 .rar 只是分发容器,和 RAR 密码破解毫无关系。下文给一套不依赖付费工具的复现方案,并说清楚 7 位以上该往哪走。
2. Excel 打开密码的加密结构与密码校验原理
2.1 打开密码走的是 EncryptedPackage,别和另外两类保护混为一谈
用 Excel 的"文件 → 信息 → 保护工作簿 → 用密码进行加密",得到的是真正的全文件加密。而"修改权限密码""工作表保护""工作簿结构保护"完全是另一套机制:它们不加密正文,只在 XML 里存一个校验值。很多人把这三类全当成"Excel 密码"去网上找破解工具,方向从一开始就错了。
| 保护类型 | 存储位置 | 是否整体加密 | Python 处理路径 |
|---|---|---|---|
| 打开密码(用密码进行加密) | OLE 容器内的 EncryptedPackage 流 | 是 | msoffcrypto / Hashcat 爆破 |
| 修改权限密码 | workbook.xml 的 fileSharing 节点 | 否 | 删除节点即可,无需破密码 |
| 工作表/工作簿结构保护 | sheetProtection / workbookProtection | 否 | 改 XML 移除保护属性 |
判断标准很简单:用 msoffcrypto 打开文件,is_encrypted()返回 False,就别在这个文件上浪费时间跑穷举了,它根本没有走到加密分支。修改权限这类保护,直接改 XML 的代价远小于破解。
2.2 Agile Encryption 的密钥派生:为什么密码能离线验证
加过打开密码的 xlsx,文件结构是一个 OLE 复合文档(CFB 容器),里面有两个关键流:EncryptionInfo和EncryptedPackage。EncryptionInfo明文存放加密参数:盐值(salt)、哈希算法(SHA-1/256/512)、spinCount(迭代次数)、AES 相关参数,以及加密后的 verifier 信息。
校验一个密码时,程序要做的是:用候选密码做 PBKDF2,迭代spinCount次派生 baseKey,再用这个 key 解密EncryptedVerifierHash,与文件里保存的 verifier 哈希比对。一致就是密码正确,不一致就是错误。整个流程完全在本地完成,不需要调用 Excel,也不存在"连续试错被锁定"的机制。
这套算法在微软的 [MS-OFFCRYPTO] 规范里叫 Agile Encryption,Office 2007 之后生成的 xlsx 基本都走这条路。旧格式 .xls 用的是另一套 Standard Encryption,参数和算法不同,但"离线可验证"这个性质完全一样。对写爆破脚本的人来说,区别只在 msoffcrypto 内部帮你选了哪套流程,外部接口不变。
2.3 openpyxl 打不开是正常的:ZIP 外面套了 OLE 复合文档
xlsx 本质是一个 ZIP 包(OOXML 容器)。加了打开密码后,这个 ZIP 整体被加密成EncryptedPackage,再塞进 OLE 复合文档。所以下面这些现象都是"正常现象":
unzip -l secret.xlsx # error: [secret.xlsx]: End-of-central-directory signature not found.openpyxl 或 pandas 直接读这类文件,会报文件损坏、或者说找不到 xl/ 目录——因为它们读到的根本不是 ZIP。msoffcrypto 做的事,就是解开 CFB 外壳、按EncryptionInfo里的参数派生密钥、把EncryptedPackage还原成一个可被 openpyxl 读取的普通 xlsx。理解这层包装,后面排错时就不会把"代码写错了"和"文件格式不对"混在一起。
3. 用 msoffcrypto-tool 写纯数字爆破:最小可复现版本
3.1 装包与单口令手动校验
前提是 Python 3.8 以上,先安装依赖:
pip install msoffcrypto-tool装完不要急着写循环,先用一个口令手动验证文件确实走的是加密分支:
python - <<'PY' import msoffcrypto with open("secret.xlsx", "rb") as f: of = msoffcrypto.OfficeFile(f) print("is_encrypted:", of.is_encrypted()) try: of.load_key(password="123456") print("该密码验证通过") except msoffcrypto.exceptions.InvalidKeyError: print("密码错误, 继续穷举") PYload_key只做密钥派生和 verifier 校验,不会把整个文件解密到磁盘,所以它非常适合当批量校验函数。注意InvalidKeyError是 msoffcrypto 明确的"密码不对"信号,其他异常(比如文件根本不是加密文件)要单独处理,不能简单当成密码错误吞掉。
3.2 完整脚本:--min/--max/--prefix/--threads 全参数
下面这份脚本是完整的纯数字穷举实现,逻辑上分三块:候选生成、单口令校验、进程池调度。文件不大,直接整段抄走就能跑。
# crack_xlsx_numeric.py import argparse import io import itertools import multiprocessing as mp import msoffcrypto DIGITS = "0123456789" def parse_args(): p = argparse.ArgumentParser(description="纯数字 Excel 打开密码穷举") p.add_argument("--file", required=True, help="加密的 xlsx/xls 文件") p.add_argument("--min", type=int, default=1, help="最短位数, 如 6") p.add_argument("--max", type=int, default=6, help="最长位数, 如 6") p.add_argument("--prefix", default="", help="记得的固定前缀, 如 2024") p.add_argument("--threads", type=int, default=0, help="进程数, 0=自动") return p.parse_args() def candidates(min_len, max_len, prefix): """按位数从小到大生成纯数字候选, 包含 0 开头组合。""" for length in range(min_len, max_len + 1): for tail in itertools.product(DIGITS, repeat=length): yield prefix + "".join(tail) def init_worker(path): """每个 worker 启动时把加密文件读进内存, 避免每次校验都做磁盘 IO。""" global SOURCE SOURCE = open(path, "rb").read() def check_one(password): """校验单个密码, 命中返回密码本身, 未命中返回 None。""" try: f = msoffcrypto.OfficeFile(io.BytesIO(SOURCE)) f.load_key(password=password) return password except msoffcrypto.exceptions.InvalidKeyError: return None def main(): a = parse_args() workers = a.threads or mp.cpu_count() stream = candidates(a.min, a.max, a.prefix) pool = mp.Pool(workers, initializer=init_worker, initargs=(a.file,)) try: for found in pool.imap_unordered(check_one, stream, chunksize=256): if found: pool.terminate() break finally: pool.close() pool.join() if found: print("[+] 打开密码 =", found) else: print("[-] 该区间未命中") if __name__ == "__main__": main()运行方式:
python crack_xlsx_numeric.py --file secret.xlsx --min 6 --max 6如果记得密码前两位是 68,就把参数改成--prefix 68 --min 4 --max 4,候选空间直接缩小 100 倍。threads留 0 时自动取 CPU 核数;在容器里跑时建议显式指定,避免被调度到物理核之外。
提示:
--min 1 --max 6会从 1 位试到 6 位,包含 000000 这类以 0 开头的组合;确认是 6 位就把 min/max 都设成 6,能少试 11 万个无谓候选。
3.3 并行、chunksize 与退出时序的工程取舍
每个候选要跑一次 PBKDF2,这是典型的 CPU 密集任务,用进程池而不是线程池。即使 hashlib 底层在某几个哈希实现里会释放 GIL,整条校验链路里还有 OLE 解析、异常处理、BytesIO 构造这些纯 Python 逻辑,线程很难把多核吃满;进程池在 Linux/macOS 上按核数线性扩展,是最稳妥的做法。
chunksize=256的含义是每个 worker 一次领取 256 个密码去校验。chunksize 太小,进程间 IPC 的次数就会盖过实际计算;太大,一旦命中,别的 worker 还在白算几千个候选。对单次校验耗时 50 毫秒上下的任务,256 是个常用折中值。
命中后的退出时序要注意:主进程拿到结果就pool.terminate(),然后用close()+join()收尾。terminate会直接结束还在跑的 worker,这没问题,因为已经不需要更多结果了。一定要把这段放在try/finally里,否则中途 Ctrl+C 时容易留下僵尸进程。
Windows 上跑这份脚本,check_one和init_worker必须在模块顶层定义,且入口要有if __name__ == "__main__"保护,这是进程池使用 spawn 模式的硬性要求。
4. 提速与边界:量级表、Hashcat 分流、常见误用
4.1 先按量级估时长:6 位和 8 位是两个世界
Excel 默认的spinCount常见是 10 万次迭代,这决定了单候选耗时。下表按单核每秒 15 个候选、8 进程每秒 120 个候选估算,只代表量级,真实速度以你机器的实测为准。
| 位数 | 候选数 | 单核耗时(约) | 8 进程耗时(约) |
|---|---|---|---|
| 5 位 | 10 万 | 1.9 小时 | 14 分钟 |
| 6 位 | 100 万 | 18.5 小时 | 2.3 小时 |
| 7 位 | 1000 万 | 7.7 天 | 23 小时 |
| 8 位 | 1 亿 | 77 天 | 9.6 天 |
所以 6 位以内值得用上面的 Python 脚本硬跑,7 位可以挂着过夜,8 位就别指望 CPU 了。真正判断该不该跑,先做一个小样本计时:只跑--min 1 --max 1,用time测十位数的耗时,再按表里的倍数换算总时长。这一步能省掉大量空等。
另外,迭代次数是写死在文件里的,不是脚本能改的。同一份文件在 Excel 和 WPS 里加密,spinCount可能不一样,速度差异会直接体现在表里"单核每秒"这个数字上。遇到特别慢的文件,第一反应应该是怀疑迭代次数偏高,而不是代码写坏了。
4.2 8 位以上转 Hashcat 掩码攻击,模式号先验证再跑
纯数字超过 7 位,把 CPU 让给 GPU 更划算。常见做法是把加密文件交给 Hashcat,用掩码?d表示 0-9:
hashcat -a 3 -m 25300 secret.xlsx "?d?d?d?d?d?d"六个?d就自动覆盖 000000 到 999999,和 Python 脚本里itertools.product的语义一致。2016 之后生成的 Office 文件多落在 25300 这个模式(SHA-512 变体),但不同版本对 Office 加密的类型划分不一致,跑之前先用这条命令确认本机模式:
hashcat --example-hashes | grep -i -B1 -A3 "Office 2016"拿到示例哈希长什么样,再对照自己文件对应的算法版本,别背死模式号。部分 Hashcat 版本能直接读取 xlsx 文件,有的版本要求先用提取脚本转成$office$*...格式的哈希串,跑不通时优先检查这一步。
GPU 吞吐量随显卡和迭代次数浮动,给不出通用数字。但 6-7 位纯数字在 25300 这类模式上通常远快于 8 核 CPU,这是它值得切工具的根本原因。切过去之后,Python 脚本的角色就变成了"先用小样本计时、估算掩码长度"的前置工具。
4.3 常见的三个误用:zip 破解器、Excel COM、改了结构保护
第一,用 ZIP/压缩包密码破解工具处理 xlsx。xlsx 打开密码是 CFB 加 Agile 加密,不是 ZIP 的 ZipCrypto 或 AES,解压类工具连EncryptionInfo流都定位不到,属于纯浪费时间。网上那些压缩包破解工具对本文场景完全无效。
第二,用 pywin32 驱动 Excel 弹窗重试。每次错误密码都要触发一次对话框,单次耗时秒级,试几百次后就可能触发 Excel 的保护性延迟,这个方案连 5 位纯数字都跑不完,没有任何竞争力。
第三,误把"结构保护/写权限"当打开密码跑。前面表格已经说明,这类保护只改 XML,msoffcrypto 会直接判定为未加密。遇到这种文件,正确做法是解包改 XML 去掉保护,而不是继续扩字典。
5. 命中之后:解密落盘、真伪校验与两个收尾技巧
5.1 把找到的密码转成明文 xlsx
脚本只验密码不落盘,命中后单独执行一次解密:
import msoffcrypto with open("secret.xlsx", "rb") as raw: f = msoffcrypto.OfficeFile(raw) f.load_key(password="找到的密码") with open("solved.xlsx", "wb") as out: f.decrypt(out) head = open("solved.xlsx", "rb").read(2) print("解密产物是 OOXML 文件" if head == b"PK" else "文件头异常")head == b"PK"是便宜又可靠的自检:load_key通过说明 verifier 校验成功,但只有解密产物能解出 PK 头,才证明整条链路真正走通。解出来的 xlsx 再交给 openpyxl 或 pandas 读取,就不会再报文件损坏了。
5.2 两个收尾技巧:内存驻留与分段计时
最后一章的落点,给两个每次都用得上的技巧。第一个是"分段计时":拿到新文件,先只跑 10 个候选,用time测出单候选耗时,再乘候选总数估算总时长。这个数字比任何网上查到的基准都可靠,因为 CPU 型号、文件spinCount、进程数三个变量全在你的机器上。第二个技巧是收进度:把输出重定向到日志文件而不是终端,避免 print 刷屏拖慢进程池;命中行用grep "\[+\]"从日志里捞出来。
如果文件很大,第 3 章脚本里的init_worker已经把全文读进内存,每个候选走 BytesIO,省去磁盘 IO 波动。这个写法对 10 MB 以内的 xlsx 完全够用,更大的文件建议先压缩或换 Hashcat,因为 OLE 解析本身的开销也会随体积上升。下次再遇到同类文件,先跑is_encrypted()确认分支,再按计时结果决定用 Python 硬跑还是切 Hashcat,这套流程就能在同一台机器上反复复用。
本文还有配套的精品资源,点击获取