ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PDF密码移除指南:区分权限密码与打开密码,用qpdf/PyPDF2本地解锁

PDF密码移除指南:区分权限密码与打开密码,用qpdf/PyPDF2本地解锁

简介:一款面向办公与学习场景的PDF密码移除小工具,专门解决加密PDF文件无法打开、复制或编辑的烦恼。它适用于经常处理受保护PDF文档的职场人士,也能帮助普通用户快速解除临时密码限制。压缩包大小仅558KB,包含9个文件,核心组件为可执行程序与动态链接库,辅助材料涵盖配置文件、帮助页面及界面示意图,结构简洁,几乎不占用存储空间。已有421人浏览学习,工具可直接运行,无需复杂安装步骤,配合内置说明文档即可快速上手。借助该工具,用户能够清除PDF打开密码,并解除部分权限限制,提升文档处理效率,适合应急使用和日常办公收藏。

1. 先分清权限密码与打开密码:为何有的PDF几秒就解,有的卡一整晚

做文档处理久了迟早撞上一种尴尬:对方发来一份带密码的PDF,口口声声说“密码就是123456”,可你把123456输进去,文件照样只能读、不能打印也不能复制。后来我才搞明白,这类文件被锁的是权限而不是内容,PDF密码移除工具根本不用破解,几秒钟就能把限制摘掉。市面流传的Password Remover资源包,大多数就是这类权限处理工具与对应的脚本批处理方案。这篇文章我把两类密码的原理、检测手段、移除命令、翻车现场全部拆开,适合不想把文件传去在线站点、希望在本机干净解决的人。

2. 检测目标PDF的加密底细:pdfinfo与qpdf两条命令看出两套锁

2.1 为什么必须先检测:R值决定解法

PDF的加密有两层:user password(打开密码)负责把文件内容真正加密,owner password(权限密码)负责控制打印、复制、编辑等操作。很多人不知道的是,owner password在大部分版本里根本没有参与内容加密,它只加密了一小段权限控制数据。PDF阅读器在打开文件时,会先解出内容,再校验owner密码来判断当前用户能不能打印、能不能复制。

这个机制导致了一个反直觉的结论:只要你知道打开密码,或者文件根本没设打开密码,owner密码的存在并不会给内容加密增加额外的解锁难度。网上能“秒解”的PDF,基本都属于R值为2、3、4这类传统加密模式,权限数据被单独处理,工具直接改写权限标记就行;而R值为6的PDF采用AES-256,权限密码的校验方式不同,处理起来要看工具是否实现了对应的解密算法。

所以我拿到任何一份加密PDF,第一步永远是摸清楚它的加密参数,而不是闷头跑工具。用qpdf的--show-encryption参数看R值、P值、加密算法,再决定走“改写权限”还是走“暴力破解”。这一步省掉,后面翻车的概率至少翻一倍。

2.2 pdfinfo 快速摸底

pdfinfo是poppler-utils里的常驻命令,多数Linux发行版自带,macOS上用brew install poppler就能装上。它不会去解密文件内容,只是读取PDF的trailer字典和Encrypt条目,所以速度极快,哪怕文件几百MB也是毫秒级出结果。

运行以下命令:

pdfinfo shot.pdf

输出里会有一行类似:

Encrypted : yes (print:no copy:no change:no addNotes:no)

Encrypted字段后跟着的括号就是权限快照。print:no表示禁止打印,copy:no表示禁止复制,change:no表示禁止修改。如果这一行显示的是“permissions: -”或者一堆yes,说明文件虽然标记了加密,但实际上没有任何受限操作,这种文件甚至可以直接用pdfinfo的-password参数带空密码打开。

这个命令只能看有没有权限锁,看不到R值。要拿R值、P值和加密算法,需要继续用qpdf。注意:pdfinfo对AES-256的PDF也能读出encrypted标志,但读不出来owner password,也无法判断权限密码和打开密码是否相同。

实际使用里有个容易忽略的点:有些文件虽然设置了owner密码,但user password为空,pdfinfo直接无密码就能列出信息,报“Encrypted: yes”,括号里全是no。这类文件就是最适合脚本批量处理的场景,你不需要任何密码就能把权限标记改掉。

2.3 qpdf --show-encryption 看关键字段

qpdf几乎是我日常处理PDF的首选工具。它既做加密也做解密,而且它做解密时是改写底层对象,不会重新压缩页面内容,所以文件体积和排版通常保持不变。这一点比大多数在线处理站强得多。

执行:

qpdf --show-encryption shot.pdf

输出里最关键的是R值:

R = 3 P = -44 User password = 123456 Owner password = abcdef (correct)

R = 3意味着这是PDF 1.4时代的RC4 40位加密,owner密码部分根本没参与正文加密。这种文件只要你拿到user password,或者user password为空,qpdf可以秒解。

R = 6是另一回事,它是AES-256加密,owner密码会被单独计算出一个哈希参与校验。遇到R = 6的PDF,直接改写权限标记的工具极容易失效,因为某些阅读器在R = 6下会重新校验owner密码的哈希。这时候真正的破解点变成了user password本身。

我把这一步当作开工前的地基。检测结果直接决定了后面的操作是“一条命令摘锁”还是“准备一张字典慢慢跑”。如果你连R值都不看就拿着工具硬怼,大概率会在一个本可以秒解的文件上浪费时间,或者在一个根本解不开的文件上重复尝试。

3. 本地移除的三种落地打法:qpdf、PyPDF2、哈希攻击的边界

3.1 qpdf 一条命令摘掉权限锁

最理想的场景是:文件没有user password,只有owner password,或者你已经知道了打开密码。这种场景在R小于6的文件上,一条qpdf命令就能结束战斗。

# 方式一:文件没有打开密码,只有权限密码 qpdf --decrypt --password='' in.pdf out.pdf # 方式二:已知打开密码,比如 123456 qpdf --decrypt --password='123456' in.pdf out.pdf

第一行命令把空字符串当作user password传给qpdf,它会完成内容解密并把权限标记改为无限制;第二行则是显式提供打开密码后做同样的操作。--decrypt的意思是解密并重写输出文件,out.pdf会继承原页面数据,不会被重新压缩。

参数说明:--password后面必须紧跟一个字符串,如果密码本身是空串,直接写--password=''就好。别漏了--decrypt,只传--password的话qpdf会进入“带密码打开”模式,输出的还是加密文件。

验证一下就明白效果了:

qpdf --show-encryption out.pdf

输出的Encryption字段应该变成“not encrypted”或者“R = 0”。如果显示R = 0,说明加密字典已经被完全摘除。

常见参数对照表在这里备用:

参数用途
--decrypt解密并输出未加密版本
--password指定打开密码,为空串时直接传''
--show-encryption显示R值、权限表、密码校验状态
--password-is-hex-key按十六进制密钥解析密码

3.2 PyPDF2 批量处理几十个文件

如果你手上有一整个目录的权限受限PDF,而且它们共用同一个打开密码,用脚本批量处理比一条条敲命令省事得多。PyPDF2的3.x版本对RC4和AES-128都支持得不错,而AES-256的完整度一般,遇到R = 6文件建议还是回到qpdf。

from PyPDF2 import PdfReader, PdfWriter from pathlib import Path src_dir = Path("./locked_pdfs") out_dir = Path("./unlocked_pdfs") out_dir.mkdir(exist_ok=True) password = "123456" for pdf_path in src_dir.glob("*.pdf"): reader = PdfReader(str(pdf_path)) if not reader.is_encrypted: continue reader.decrypt(password) writer = PdfWriter() for page in reader.pages: writer.add_page(page) with open(out_dir / pdf_path.name, "wb") as f: writer.write(f) print(f"已处理: {pdf_path.name}")

逻辑分三步:先通过is_encrypted判断这个文件是否加密;再用decrypt传入打开密码;最后逐页复制到PdfWriter并写入新文件。这里有个细节,writer.write时不要在原目录直接覆盖,因为PyPDF2在write前会整体读取原文件流,覆盖同名文件容易把输入文件截断,导致写出一个坏PDF。输出目录单独建,写完之后再自己核对一遍文件大小,凡是明显缩水的那批基本就是解密异常。

参数说明:password变量对应这批文件的统一打开密码。如果你的文件没有打开密码,只有权限密码,decrypt('')或者decrypt(' ')可能出现异常,我一般先试空串,再试常见的默认密码组合,比如“admin”“ 123456”,还不行就回头查qpdf --show-encryption的输出,看User password到底是空还是某个固定值。

批量场景里异常文件不能直接让脚本崩掉,常见的做法是加一个异常捕获,把失败文件单独打印出来:

try: reader.decrypt(password) except Exception as e: print(f"失败: {pdf_path.name} - {e}") continue

加上这段之后,脚本会跳过打不开的文件继续往后跑,全部结束时再单独处理失败清单里的文件,而不是整个任务卡死在一个坏文件上。

3.3 哈希攻击只对弱口令有意义

如果user password也是未知的,并且文件确实被内容加密了,qpdf就没法走捷径了,唯一的暴力路径是把PDF的加密哈希提取出来喂给hashcat。

先确认hashcat能识别的模式:

pdf2john.py locked.pdf > locked.hash hashcat -m 10700 locked.hash dict.txt

-m 10700对应PDF 1.4到1.7的RC4/AES-128加密模式;-m 10420对应PDF 1.7的AES-256加密模式。用哪个模式取决于qpdf --show-encryption里显示的R值,R小于4走10700,R=6走10420。

参数说明:dict.txt是字典文件,里面每一行是一个候选密码。hashcat默认会尝试直接命中,你也可以在后面追加规则文件做变形,比如hashcat -m 10700 locked.hash dict.txt -r rule.rule。规则会把字典里每个词做大小写、加数字、加符号等变换,命中率会高一些,代价是跑的时间成倍上涨。

需要清醒一下:哈希攻击只对弱口令有效。PDF的密码机制里,user password的哈希是经过多次迭代的,AES-256模式的哈希迭代次数相当高,GPU速度再快也扛不住12位以上的随机密码。我见过有人在群里问“怎么破解12位混合密码”,这种问题本身就没有工程解。哈希攻击的正确使用场景是批量回收内部文档,密码是某个旧账号或常见姓氏加生日,字典里大概率存在。那不是去对抗加密,是去猜一个习惯。

对纯数字短密码,可以跳过字典直接mask攻击,比如跑6位数字:

hashcat -m 10700 locked.hash -a 3 ?d?d?d?d?d?d

这种姿势适合内部测试环境里已知密码规则的文件,不建议作为日常默认方案,因为真正安全的PDF系统本来就不应该被mask攻击在合理时间内命中。

4. 避坑与常见问题:四个现场翻车记录

我在这类资源上踩过的坑,基本都能归到同一个根因:没做检测就把工具往文件上怼。下面是四个反复出现的翻车案例,每个都按现象、原因、解决展开说。

4.1 权限移除后PDF反而打不开

现象:我用qpdf处理完一个文件,out.pdf打不开,报“文件已损坏或密码错误”。pdfinfo看输出文件,Encrypted字段变成了yes,但括号里的权限全是允许,文件却根本无法正常渲染。

原因:这个PDF同时有user password和owner password,而我直接用--decrypt却没提供user password,qpdf在写入过程中没能完成内容解密,最终产出一个既保留了加密残留、又丢掉了权限控制的畸形文件。R值为6的文件尤其容易出现这种情况。

解决:先跑qpdf --show-encryption确认User password是否为空。如果不为空,把user password补上再执行:

qpdf --decrypt --password='123456' in.pdf out.pdf

执行后再用pdfinfo看输出文件,Encrypted必须变成no才算真正完成。从那以后我处理带双重密码的文件,都会先显式确认User password那一栏,绝不直接省略。

4.2 在线工具一转,页面模糊、文本层消失

现象:把文件传到某个在线PDF解锁站点,转回来发现页面的矢量文字全部变成位图,放大后边缘发虚,文字无法选中,文件体积涨了一倍还多。打印出来效果尚可,但想复制一句话回邮件就抓瞎了。

原因:在线站点用的是渲染再截图的方式,而不是底层改写。它们把每一页画成图片输出,等于用图片替换了整个内容。这种方案对纯扫描件没问题,对文本型PDF就是灾难。

解决:能本地处理的资源包就不要用在线工具。如果本地工具解不开,宁可放弃这个文件,也不要拿着图片化PDF去交差,尤其是需要二次编辑或文字提取的场合。我一般会在交付前用pdftotext验证输出文件是否保留文本层,没有文本层的一律返工。

4.3 qpdf报invalid password但阅读器能开

现象:qpdf --decrypt --password='123456' 报错invalid password,但PDF阅读器用123456能正常打开,还能打印。

原因:密码本身含有不可见的字符,比如ASCII的零宽空格、全角空格,或者文件生成时在密码末尾拼接了换行符。阅读器在打开时做了宽松处理,qpdf则严格按照字节比较,差一个字节就拒绝。

解决:把密码复制到十六进制编辑器里查一下结尾,常见做法是用qpdf的--password-is-hex-key参数。如果密码是十六进制字符串,换个方式传:

qpdf --password-is-hex-key='313233343536' --decrypt in.pdf out.pdf

这个参数会把输入的十六进制串直接当二进制密钥用,绕开字符层比较的坑。注意它只解决密钥格式问题,如果密码确实不是这个值,该报错还是会报错。

4.4 字典攻击跑了一夜一无所获

现象:hashcat跑了一整晚没有hit,GPU占用率看曲线确实在跑,但就是没结果。命令没报错,日志里也没有异常,属于静默失败。

原因:字典里没有对应密码,或者模式选错了。R=6的文件走了-m 10700,hashcat直接忽略或一直报错;又或者密码本身就是长口令,不在字典范围内。

解决:拿到hash先看文件头,确认是10700还是10420。再看hash迭代参数,R=6的文件hash开头会有明显标志。如果hashcat的统计窗口显示密码都在8位以下而实际密码是14位,唯一合理的收尾是停止爆破,向提供方要密码。哈希攻击不是万能钥匙,跑一天没结果就该止损,而不是加规则继续烧显卡。

5. 收尾验证:pdfinfo、pdftotext与一次真实打印确认“真移除”

移除完之后不要直接交付。我习惯做三件验证,缺一不可。

5.1 pdfinfo复核权限标志

pdfinfo out.pdf | grep Encrypted

如果输出是“Encrypted: no”,说明加密标记已经消失。如果输出还是yes,说明qpdf写完后文件仍被某个阅读器级安全策略标记,这时候用qpdf --show-encryption再看R值,R小于等于0才真正干净。这个命令只花几毫秒,但能拦住大部分“以为解开了”的场合。

5.2 pdftotext验证文本层

pdftotext out.pdf - | head -20

能输出文本内容说明内容层已经完整解密。如果输出空,说明文件可能是扫描件,没有文本层,这是正常情况,不代表解密失败。扫描件要改用打印验证。这一步同时也能确认在线工具那张截图坑没踩到。

5.3 一次真实打印

把out.pdf送到虚拟打印机或本地打印机,观察打印任务是否被弹窗拦截。权限锁移除后,打印应该直接进入队列。如果是扫描生成的PDF,这一步更是唯一可靠的内容完整性验证方式。

这三步走完,输出文件才算真正可交付。之前在某次标书文件处理翻车之后,我每次做完PDF密码移除都会强制走一遍pdfinfo、pdftotext、打印验证三步,缺一不可,这个习惯直接救回过好几次交付事故,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进