ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Excel工作表保护密码忘记?用Python脚本一键移除,附完整代码与避坑指南

2026/9/26 7:58:40 拓冰建站 浏览量
Excel工作表保护密码忘记?用Python脚本一键移除,附完整代码与避坑指南 上周同事拿着一份Excel文件来找我说工作表被加了保护密码怎么都想不起来了。他自己上网找了一圈“密码移除工具”不是要关注公众号就是得付费还有的下载下来直接给电脑塞了一堆捆绑软件。我当时正好手头不忙就花了二十来分钟写了一个Python小脚本拖进去一个xlsx回车保护直接清掉全程不碰密码也不需要暴力破解。这篇文章就把这个工具的完整思路、代码和几个容易踩的坑都记录下来给同样被“忘了工作表密码”困扰的人一个可靠的解决方案。先说清楚这个工具处理的是工作表级别的密码保护也就是Excel里“审阅 - 保护工作表”加的那道锁。它和“打开文件需要密码”是两回事后者是对整个文件做加密处理逻辑完全不同这篇不涉及。另外在使用这个工具前请确认你拥有该文件的合法处理权限它最适合的场景就是“文件是自己建的密码忘了”这种。1. 先搞懂原理xlsx加密保护的真相1.1 工作表密码保护与“文件打开密码”是两码事很多人一听到“Excel密码”就混为一谈其实Excel里有好几层完全不同的密码机制打开密码对文件本体做加密不知道密码Excel连文件内容都读不出来。这属于加密技术范畴想绕过的唯一途径就是暴力破解或字典攻击计算量大耗时不稳定。工作表保护在Excel界面里通过“审阅 - 保护工作表”设置。文件本身并没有被加密Excel完全可以正常打开只是在修改单元格、插入行列、删除内容等操作上做了权限限制弹窗提示“工作表已受到保护”。工作簿结构保护防止用户对工作表进行“重命名、移动、删除、隐藏”等结构操作密码写在workbook.xml里。VBA工程密码锁的是宏代码靠修改XML的简单方案无法处理。本文的“一键移除”针对的是第二种工作表保护。因为这种情况下密码信息并没有对Excel文件做真正意义上的加密它只是写在文件内部的XML结构里。这就给了解除保护一个很大的操作空间。1.2 藏在XML里的“开关”sheetProtection到底是什么xlsx文件本质上是一个zip压缩包。你可以把后缀改成.zip然后直接解压会看到里面有一堆XML文件、关系文件、资源文件夹。每个工作表对应的内容都在xl/worksheets/目录下通常是sheet1.xml、sheet2.xml这样的命名。当你对一个工作表启用保护时Excel就会在这个sheet对应的XML里写入一个sheetProtection节点。正常未加保护的工作表这个节点是不存在的。我用一个加了密码的测试文件实际看了一下保护节点的内容大致长这样sheetProtection sheet1 objects1 scenarios1 passwordA5B2C3D4 algorithmNameSHA-512 hashValue... saltValue... spinCount100000/其中sheet1表示“允许编辑”的开关被关掉了objects1表示锁定图形对象scenarios1表示锁定方案。后面的password或hashValue、saltValue、spinCount则是密码的哈希信息用来验证你输入的密码是否正确。所以移除工作表保护的思路很直接找到这些XML文件里对应的sheetProtection节点把它删掉重新打包成xlsxExcel打开后就不会再有任何保护限制。整个过程不需要去逆向Excel的加密算法也不需要解开密码哈希。它有点像门锁密码忘了但你不去撬锁而是直接把那扇门的锁舌拆了——因为门本来就是开着的锁只是装饰。1.3 为什么不直接“猜密码”哈希校验与破解成本可能有人会问既然都拿到哈希了为什么不直接暴力破解密码这里需要了解一下Excel的密码哈希机制。旧版Excel2007之前使用一种相对简单的XOR哈希算法网上确实有一些小工具直接算。但从新版Office开始密码哈希换成了类似PBKDF2的迭代算法用salt加密码做SHA-512连续迭代10万次再把结果存进XML里。这意味着破解一个稍微像样的密码要么靠GPU跑很久要么靠字典撞完全不可控。而移除sheetProtection节点这个操作时间和密码复杂程度完全无关。哪怕是8位以上的强密码处理时长也基本相同。这就是“移除保护”和“找密码”在工程成本上的根本区别。2. 核心实现从zip入手绕过保护2.1 为什么不用openpyxl直接改说到用Python处理Excel大部分人第一反应是openpyxl。它确实能读写xlsx也提供了sheet.protection.sheet False这类解除保护的API官方文档写得明明白白。那为什么我这里不推荐直接用openpyxl写这个工具问题出在openpyxl的保存机制上。openpyxl在读取文件时会把所有工作表、样式、公式、图片引用、数据透视表、图表等信息解析成Python对象保存时再把这堆对象重新序列化成XML。这个过程即便你只改动了一个地方也可能给文件带来大量不可见的“副作用”宏文件.xlsm直接打开再保存vbaProject.bin有概率丢失或被破坏。复杂图表、数据透视表缓存重新序列化后部分高级结构会损坏或失去原有格式。条件格式、数据验证、自定义样式某些边缘语法在openpyxl的解析和写出之间会发生微妙变化导致Excel打开时提示“文件损坏是否尝试恢复”。最近使用的字体、主题、打印设置重新保存后可能被重置。也就是说openpyxl能处理90%的普通文件但对于内部结构复杂的文件一个看起来无关紧要的保存动作就是给自己的数据埋雷。我们要做的操作其实非常“外科手术化”只找出sheetProtection节点把它从XML里摘掉其他所有内容原封不动。既然这样最安全的方式就是在zip字节层面直接操作而不是把整个文件解析重建一遍。2.2 代码整体结构解压、手术、回填整个工具的流程可以拆成四步用zipfile打开xlsx文件遍历所有内部条目定位到xl/worksheets/目录下的XML文件。读取XML内容用正则表达式匹配sheetProtection ... /节点注意节点可能带一堆属性也可能是自闭合标签形式。对匹配到的节点做移除操作。重新写一个新的zip文件把处理后的XML回填进去其他文件保持原样。这里有一个容易被新手忽略的细节写新zip时每个文件条目的压缩方式最好沿用原文件的压缩方式。如果你把原来用ZIP_STORED不压缩存储的条目全改成ZIP_DEFLATEDExcel一般也能打开但某些第三方工具对zip内部结构比较敏感稳妥起见尊重原始压缩方式是对的。2.3 关键代码段逐一拆解附完整脚本先看完整脚本只有不到50行import re import zipfile import shutil from pathlib import Path def remove_sheet_protection(src_path, dst_pathNone): src_path Path(src_path) if dst_path is None: dst_path src_path.with_name(src_path.stem _unlocked src_path.suffix) tmp_path dst_path.with_suffix(.tmp.zip) with zipfile.ZipFile(src_path, r) as zin: with zipfile.ZipFile(tmp_path, w) as zout: for item in zin.infolist(): data zin.read(item.filename) if item.filename.startswith(xl/worksheets/) and item.filename.endswith(.xml): text data.decode(utf-8, errorsignore) new_text, count re.subn( rsheetProtection\b[^]*?(?:/|\s*/sheetProtection), , text, flagsre.IGNORECASE | re.DOTALL ) if count: print(f[移除] {item.filename}: 删除 {count} 处保护节点) data new_text.encode(utf-8) zout.writestr(item, data) shutil.move(str(tmp_path), dst_path) return dst_path if __name__ __main__: import sys for f in sys.argv[1:]: out remove_sheet_protection(f) print(f已生成: {out})这段代码里最核心的是那个正则表达式rsheetProtection\b[^]*?(?:/|\s*/sheetProtection)解释一下sheetProtection\b精确匹配标签名的开头\b防止匹配到sheetProtectionExtra这类名字。[^]*?匹配标签内部属性非贪婪直到遇到。(?:/|\s*/sheetProtection)两种结束形式都要考虑。一种是自闭合写法sheetProtection ... /另一种是成对写法sheetProtection .../sheetProtection。实际文件中绝大多数是自闭合但保险起见两种都处理。另外用re.subn而不是re.sub是为了拿到替换次数方便在命令行输出里确认“这个工作表确实处理过了”。关于编码xlsx里的XML默认是UTF-8之所以加上errorsignore是为了防止个别文件里包含特殊二进制字符导致decode直接抛异常。因为我们要处理的只是XML文本忽略少量异常字符影响不大。写完这个脚本后用我们前面那个测试文件跑一下输出效果是这样的[移除] xl/worksheets/sheet1.xml: 删除 1 处保护节点 已生成: test_unlocked.xlsx用Excel打开test_unlocked.xlsx直接就能编辑单元格了保护弹窗消失。再用zip解压看里面的XMLsheetProtection节点确实已经不在了。3. 一键体验从命令行到拖拽GUI3.1 批处理模式一次清空整个文件夹上面的脚本已经支持命令行传多个文件python remove_protect.py 文件1.xlsx 文件2.xlsx 文件3.xlsx但日常最方便的是“拖拽式操作”把多个文件直接拖到脚本或批处理文件上。Windows下可以写一个.bat文件echo off cd /d %~dp0 python remove_protect.py %* pause把remove_protect.py和这个.bat放在同一个目录以后把xlsx文件拖到这个.bat上松开鼠标就会自动调用Python脚本逐个处理。所有输出文件都以原文件名_unlocked.xlsx的格式保存在同目录不会覆盖原始文件这就天然带了一层“安全备份”的属性。如果还想更彻底一些可以在脚本里加一个参数来控制是否覆盖原文件。不过我的习惯是永远保留一份原文件万一移除后Excel仍提示异常还能切回原始文件排查不至于把自己逼进死胡同。3.2 用tkinter写个简单的窗体命令行工具对习惯图形界面的同事来说还是有点门槛。如果想要一个真正的“一键移除”体验可以用Python自带的tkinter写一个极简GUI一个按钮选文件一个按钮开始处理一个文本框输出日志。import tkinter as tk from tkinter import filedialog, messagebox from remove_protect import remove_sheet_protection def pick_files(): files filedialog.askopenfilenames( title选择要解锁的Excel文件, filetypes[(Excel工作簿, *.xlsx *.xlsm)] ) log_text.delete(1.0, tk.END) for f in files: try: out remove_sheet_protection(f) log_text.insert(tk.END, f成功: {f} - {out}\n) except Exception as e: log_text.insert(tk.END, f失败: {f} - {e}\n) if files: messagebox.showinfo(完成, 处理结束请查看下方日志。) root tk.Tk() root.title(Excel工作表保护一键移除) btn tk.Button(root, text选择文件, commandpick_files, width30, height3) btn.pack(pady20) log_text tk.Text(root, height12, width80) log_text.pack(padx10, pady10) root.mainloop()界面上就一个按钮选择文件后自动批量处理日志区会显示每个文件的处理结果。这个GUI对不懂命令行的用户已经足够友好。3.3 打包成exe给同事用如果不希望依赖Python环境可以用PyInstaller打包pip install pyinstaller pyinstaller -F -w remove_protect_gui.py-F表示打成一个单独的exe-w表示运行时不弹出黑色控制台窗口因为GUI模式下不需要。打包完在dist目录下就能找到exe文件。这里有两个实操提醒杀毒软件误报。这类带有“移除密码”功能的工具容易被杀软判定为风险程序因为行为特征和某些破解工具相似。自用的没问题发给同事如果被杀软拦截可以考虑让同事加白名单或者干脆直接分享源码让对方在本地跑。体积问题。PyInstaller打的exe通常有20到50MB因为内置了Python解释器和相关库。如果介意体积建议用conda或venv创建一个精简环境再打包如果只是自己用直接跑源码反而最省事。4. 边界、坑与安全提醒4.1 老版.xls文件为什么不管用细心的读者会发现脚本里我只处理了.xlsx和.xlsm没有处理老版本的.xls。原因是.xls是Excel 97-2003时代的二进制格式BIFF根本不是zip结构内部也没有sheetProtection这种XML节点。如果手头只有.xls文件可以先在Excel里另存为.xlsx格式再用脚本移除保护。注意另存为的操作要求你先能打开文件——.xls的工作表保护不会阻止文件打开所以这点是可以做到的。批量场景下也可以用LibreOffice的命令行参数做格式转换soffice --headless --convert-to xlsx 旧文件.xls转换完成后再跑脚本虽然多了一步但总算把老格式也覆盖到了。4.2 容易被误解的“保护”边界这篇工具处理的是工作表级别保护对应的是xlsx内部xl/worksheets/sheetN.xml里的节点。有几类情况它处理不了工作簿结构保护密码信息在xl/workbook.xml里的workbookProtection节点。如果你想禁止别人重命名、删除、隐藏工作表那属于结构保护需要修改的是另一个文件。思路类似但节点位置不同。共享工作簿保护在“审阅 - 共享工作簿”里设置的保护处理逻辑更复杂而且Excel 365已经在逐步移除相关功能。VBA工程密码宏代码保护写在vbaProject.bin内部靠修改XML是不行的需要专门的VBA密码处理手段。打开文件密码这是对整个文件流的加密zip解压出来的内容本身就是密文连sheet1.xml都读不到。很多人拿着这个脚本去处理“打开时需要密码”的Excel然后发现毫无效果其实就是没弄明白这几个保护层级之间的区别。建议在使用工具前先确认清楚你的文件能正常打开、能看见数据只是单元格不让编辑那才是这个工具的适用范围。4.3 几个容易忽略的细节坑实际测试过程中我遇到过几个不在预料之内的情况记录一下空工作表节点。如果某个工作表名对应的XML里没有sheetProtection节点正则替换次数为0脚本会跳过不会报错。这是正常的。文件中多个工作表受保护。xl/worksheets/目录下每个sheet对应的XML都会检查因此支持批量移除同一份文件里多个工作表的保护。找不到就是没有。主控XML里也可能带保护信息。如果你用了开发者工具自定义控件或者在xl/drawings/、xl/charts/等位置也存在与受保护工作表相关的缓存定义可能需要一并检查。不过就常规场景而言工作表保护就存在sheetN.xml里上面的脚本已经覆盖到了。超大文件的内存占用。zin.read(item.filename)会把整个文件条目读进内存一般几十MB的Excel没什么压力。但如果遇到几十万行、几百MB的超大工作簿内存会比较高建议在脚本里用item.file_size大致判断一下再决定是否读取。4.4 安全与合规提醒严格来说这个工具移除的是“保护开关”并不是“找回密码”。它不产生任何密码也不会让你知道原来设置的密码是什么。它适合的文件是你自己设置过密码又忘记的或者是已经获得权限可以解除保护的文档。出于安全习惯建议处理前先复制一份原文件备份防止操作过程中文件损坏。虽然字节级修改已经很保守但备份是零成本的万一出现意外也不会慌。5. 还能怎么扩展从移除到改密5.1 保留保护但替换密码的玩法有时候你不是想完全移除保护而是想“换一个自己记得住的密码”。这种情况下没必要先移除再重新保护因为那样会产生两次文件修改流程。更优雅的做法是在XML里直接替换掉sheetProtection节点的哈希属性。旧版Excel的密码哈希算法相对简单如果文件里的sheetProtection用的是passwordXXXX这种四字符哈希格式可以直接用Python计算新密码的哈希值然后替换def legacy_hash(pwd): h 0 for ch in pwd: h h ^ ((h 5) 0xffffffff) ord(ch) return format(h 0xffff, 04X)不过新版Office插入了saltValue和spinCount机制直接手工构造比较麻烦。更省事的方案是先用上面的工具移除保护再用openpyxl重新设置一个你自己记得住的密码from openpyxl import load_workbook wb load_workbook(test_unlocked.xlsx) ws wb.active ws.protection.sheet True ws.protection.password 新密码 wb.save(test_relocked.xlsx)这个做法会引入openpyxl重新保存的格式风险所以我的建议是如果文件结构简单、没有大量宏和图表直接这么干没问题如果文件很复杂还是保留“移除保护”的版本平时通过文件系统的权限管理来控制访问。5.2 更进一步把脚本打包成“右键菜单”工具前面提到了拖拽到.bat和GUI两种交互方式其实Windows下还有一个体验更好做法把自己写的脚本注册到资源管理器的“发送到”菜单里这样在任意Excel文件上右键选择“发送到 - 移除工作表保护”就能直接处理连拖拽都省了。在资源管理器地址栏输入shell:sendto打开“发送到”目录把脚本的快捷方式放进去即可。如果是脚本需要手动创建一个.cmd批处理文件放在那里python D:\scripts\remove_protect.py %1 pause之后每次右键文件都能直接把文件路径传进去。这个操作对Windows 10和Windows 11都有效。5.3 日志与错误输出如果要把工具交给不太会用电脑的人建议在批处理和GUI版本里都加上日志功能哪怕是简单的打印输出也行。别小看这行日志它能帮你判断文件是不是真的处理成功。比如某些文件虽然扩展名是xlsx但内部可能有损坏zipfile读取时会报错有日志你就能定位是哪一步出了问题。我在使用中积累的习惯是处理一份文件的时间记录一下如果某个文件处理时长明显异常多半是文件内部有我们没预料的特殊结构需要单独检查。这个工具从写出来到现在我自己用了很多次也被同事借走了好几回处理过最大的一个文件是一份带数据透视表和条件格式的19MB工作簿用ZipFile方式全程只用了不到3秒文件打开完全正常。相比网上下载那些收费工具的提心吊胆自己写脚本控制全过程至少所有的改变都由自己掌控。如果有这方面需求建议直接拿代码去跑一下改动不多却能省下大量重复劳动。