摘要
我用 Python 脚本生成了 7 份 2 页的测试 PDF,内容全是占位文字。拿库一查有 6 份被报成「已加密」。可放进 Chromium 和 Firefox 自带的阅读器里真正弹出密码框的只有 1 份。这个对不上的数字让我卡了一个晚上,这篇笔记就从这里查下去,前半部分讲两种密码各管什么。打开密码就是用户密码,它把内容真正锁了起来让不知道密码的人一个字都看不到。权限密码是所有者密码。它在文件里写下一串权限位标明作者不允许打印、复制、编辑或批注。这些限制要靠打开它的软件配合执行。后半部分是我在两个阅读器和两个库上逐项对出来的结果,还有一个压缩工具遇到这两种文件时的处理方式。
结论先放在这里。打开不要密码却被工具说成加密的 PDF 是只设了权限密码。它的加密字典是完整的。内容流也真被加密过,只是用户密码为空,阅读器打开时会自动拿空密码解开它。同一份全禁的文件在 Chromium 149 里打印和复制都被拦住了,绘制却被放行。Firefox 151 在默认设置下三样都不拦。打开它的权限执行开关以后打印和编辑被拦住了,复制还是没拦。把 AES-256 换成 RC4-40 以后这些结果一点没变,影响结果的只有权限位。收到这种文件想要可编辑的版本只有两条正规的路。一条是找原作者要一份不带限制的版本,另一条适合文件本来就是自己的情况。还记得所有者密码的话就回到当初生成它的软件里输入所有者密码再重新导出一份。
版本声明
实测是 2026-09-30 在一台装 macOS 26.5.2 的 Apple M4 Mac 上跑的。阅读器看了两个,一个是 Chromium 149(149.0.7827.55)开源构建自带的 PDF 阅读器。另一个是 Firefox 151.0 自带的阅读器,它内置的 pdf.js 是 5.7.195。这两个都不等于日常装的 Chrome 正式版和 Firefox 正式版。页面里当库用的 pdf.js 6.3.289 我在 Chromium 149、Firefox 151 和 WebKit 26.5 三个内核里各跑了一遍。WebKit 26.5 也不是 Safari 正式版。Python 这边是 3.9 配 pypdf 6.10.2 和 PyMuPDF 1.26.5,PyMuPDF 底下是 MuPDF 1.26.10。文中凡是说到某个阅读器怎么做的结论都只对这几个版本成立。
适用边界
样本是我自己用脚本生成的 7 份 2 页占位 PDF。标题统一叫「示例合同(占位文字,仅供测试)」,每份 66 到 69 KB。它们不是真实合同也不是谁发给我的文件。我只测了 PDF 最常见的 Standard 安全处理器。证书加密、只加密附件和带数字签名的文件都没测。Acrobat Reader、macOS 预览、Safari 正式版、WPS 和手机上的阅读器我也都没测,文中不对它们下任何结论。这篇只讲两种密码是什么、各自限制了什么,以及文件的主人和收件人该怎么正规处理。怎么去掉别人文件上的限制不在这篇里,我也不会写。
文章目录
一、PDF显示已加密为什么不用输密码
- 1.1 七份测试文件里只有一份要密码
- 1.2 不要密码的文件也带着加密字典
- 1.3 两个库对同一份文件说法相反
二、PDF打开密码和权限密码有什么区别
- 2.1 打开密码把内容锁在密码框后面
- 2.2 用户密码为空谁都能解开内容
- 2.3 权限位写着作者不允许做的事
三、PDF不能复制不能打印是谁在拦
- 3.1 Chromium拦住了复制和打印
- 3.2 Chromium没有拦绘制
- 3.3 Firefox默认设置下三样都不拦
- 3.4 打开权限执行开关后复制仍然没拦
- 3.5 库只把权限位交给调用方
四、PDF加密算法越强限制越严吗
- 4.1 四种算法拦下的操作完全一样
- 4.2 输入打开密码以后限制照旧
五、压缩工具为什么拒绝加密的PDF
- 5.1 两种加密文件压缩时都报失败
- 5.2 拒绝的原因界面上没有写
六、PDF已加密不能编辑该找谁
- 6.1 先看打开时有没有密码框
- 6.2 只有权限密码就找原作者重发
- 6.3 自己的文件用原软件重新导出
七、适用边界与风险
八、测量方法的坑
九、还没解决的
参考资料
一、PDF显示已加密为什么不用输密码
1.1 七份测试文件里只有一份要密码
我最近在做的一个个人项目要读 PDF,读之前想先判断一下文件能不能处理。网上搜「PDF 已加密 不能编辑」能看到不少人在问同一件事。文件明明双击就能打开并且正文看得清清楚楚。可一复制就什么都没有。换个工具又提示已加密。我对 PDF 加密的印象一直停留在压缩包密码那一层,觉得加密就得输密码。这些帖子我看了一圈也没看明白。我干脆自己生成几份文件来试,免得拿别人的文件做实验。
脚本一共生成了 7 份文件。不加密的 S0 和设了打开密码的 S5 都当对照组。剩下 S1、S2、S3、S4、S6 五份只设了权限密码,也就是用户密码留空、只填所有者密码。这五份我故意换了不同的算法和限制组合。下面这张表是它们的设置,限制那一列只是文件里声明的内容。阅读器执不执行是后面几章的事。
| 文件 | 打开密码 | 加密方式 | 声明的限制 |
|---|---|---|---|
| S0 | 无 | 无 | 无 |
| S1 | 无 | AES-256 | 全禁,只留无障碍提取 |
| S2 | 无 | RC4-128 | 同 S1 |
| S3 | 无 | AES-128 | 只禁复制 |
| S4 | 无 | AES-256 | 只禁打印 |
| S5 | 有 | AES-256 | 同 S1 |
| S6 | 无 | RC4-40 | 全禁,连无障碍提取也禁 |
生成完我先用 pypdf 挨个问了一遍这份文件加没加密。除了 S0 以外的 6 份全都回答 True。然后我把 7 份文件依次拖进 Chromium 149 和 Firefox 151 自带的阅读器。S0 到 S4 和 S6 都直接显示出正文并且界面上没有任何提示,也看不出它们跟不加密的 S0 有什么区别。只有 S5 一打开就被拦在了密码框前面。6 份加密却只有 1 份要密码,我当时在笔记里写了一句「加密 ≠ 要密码?」。后面这一整章都是在回答这个问号。
1.2 不要密码的文件也带着加密字典
我最先怀疑的是 pypdf 报错了,也许那 5 份文件只是带了个标记但内容其实没加密。我又用 PyMuPDF 把每份文件的加密描述打了一遍。S1 写着「Standard V5 R6 256-bit AES」。S2 是 RC4 128 位,S3 是 AES 128 位,S6 是 RC4 40 位。这些描述来自文件里的加密字典,也就是 PDF 规范里的 /Encrypt 字典。它记着用哪种安全处理器、哪个版本和多长的密钥,还有一个叫 P 的整数。P 就是权限位。
字典是完整的,内容流也真被 AES 或 RC4 加密过。是我猜错了。工具说「已加密」是实话,这 5 份文件确实是加密文件并不是只挂了个标签。那它们为什么不要密码?答案藏在「用户密码留空」这几个字里,我到第二章才真正想通。这一节先记下一个结论,工具说的加密和阅读器要不要密码是两个问题。前一个问的是文件里有没有加密字典,后一个问的是解开它需不需要你知道点什么。
1.3 两个库对同一份文件说法相反
查的过程中还有一个小插曲。pypdf 和 PyMuPDF 都有一个叫 is_encrypted 的属性,名字一模一样。对只设权限密码的 S1 来说 pypdf 返回 True 但 PyMuPDF 返回 False。我一开始只看了 PyMuPDF 的结果就差点在笔记里写成「S1 没加密,是 pypdf 搞错了」。后来去翻文档才知道两个库问的根本不是同一件事。pypdf 的 is_encrypted 问的是文件里有没有加密字典,S1 有这个字典,答案就是 True。PyMuPDF 的 is_encrypted 问的是这份文件眼下还锁没锁着,S1 在打开时已经被空密码解开了,它就回答 False。PyMuPDF 里真正用来区分打开密码的是另一个属性 needs_pass。7 份文件里只有 S5 的 needs_pass 是 1,其余都是 0。下面是我把五种看法放在一起对的结果。
| 看法 | S0 无加密 | 只有权限密码的五份 | S5 打开密码 |
|---|---|---|---|
| Chromium 149 阅读器 | 直接显示 | 直接显示,无提示 | 弹出密码框 |
| Firefox 151 阅读器 | 直接显示 | 直接显示,无提示 | 弹出密码框 |
| pdf.js 6.3 页面库 | 无加密字典 | 有加密字典 | 要求输入密码 |
| PyMuPDF | needs_pass 0 | needs_pass 0 | needs_pass 1 |
| pypdf | is_encrypted False | is_encrypted True | is_encrypted True |
这张表横着读就清楚了。阅读器和 needs_pass 回答的是要不要密码,五份只有权限密码的文件跟 S0 站在一边。pypdf 和 pdf.js 回答的是有没有加密字典,这五份又跟 S5 站在一边。网上那些「明明能打开却说加密了」的困惑大概就是两边各看了一种说法。我自己的项目里后来都是把两个问题分开问,因为只看一个布尔值是会判错的。
二、PDF打开密码和权限密码有什么区别
2.1 打开密码把内容锁在密码框后面
PDF 的 Standard 安全处理器其实给了两个密码位。一个叫用户密码,平时说的「打开密码」就是它。另一个叫所有者密码,平时说的「权限密码」就是它。两个密码可以都设也可以只设一个。S5 两个都设了,S1 到 S4 和 S6 只设了所有者密码并把用户密码留空。先看设了用户密码的 S5。它在 Chromium 149 里一打开就是下面这个对话框。

这张图里要看的是背景。对话框后面是一片全黑的页面,一个字都没渲染出来。标题写着「需要密码」,下面一行是「本文档设置了密码保护,请输入密码。」。右下角只有一个「提交」按钮。没有「先看看」或者「跳过」这类选项,不输对密码的话阅读器连一页都不给你看。只设权限密码的五份文件在同一个阅读器里从来没弹过这个框,这就是两种密码最直观的区别。
Firefox 151 的英文界面长得不一样但意思完全相同。

Firefox 这一版的框里只有一句「Enter the password to open this PDF file.」,下面是 Cancel 和 OK 两个按钮。框后面的页面同样是空的。换成库来读也一样,pdf.js 6.3 读 S5 时会触发要密码的回调并报出 PasswordException。PyMuPDF 读 S5 报出的 needs_pass 是 1,连加密方式都读不出来。打开密码之所以真能锁住内容,是因为解密内容流用的密钥要从密码推出来。我拿 PyMuPDF 的 authenticate 试了三种输入。输错密码返回 0,输用户密码返回 2,输所有者密码返回 4。两个密码一个都不知道的人拿不到解密需要的东西,只有在这种情况下加密算法强不强才有意义。
2.2 用户密码为空谁都能解开内容
回头再看只设权限密码的那五份,问题就出在「用户密码为空」上。PDF 打开时阅读器会先拿空密码去试用户密码这一关。空密码谁都知道,一试就过。内容流照样是用 AES 或 RC4 加密过的,只是解开它要的「密码」是一个人人都知道的空字符串。这五份文件在任何阅读器里都能直接显示,连提示都不会弹。
这一点我是用两个数对出来的。pdf.js 6.3 当页面库读 S1 时拿到了 169 个非空白字符,跟不加密的 S0 一模一样。PyMuPDF 读 S1 第 1 页是 195 个字符也跟 S0 相同。内容一个字没少,在打开那一刻就已经解出来了。我把这两个数写在这里只是为了说明权限密码在密码学上并没有锁住内容。这不等于拿库去处理别人受限的文件就没问题,文件的作者明明白白写了不希望被复制。想明白这一层以后,我对「已加密」三个字的理解才算转了过来。只设权限密码的文件确实加密了,可这个加密挡不住任何一个打开它的人。它真正起作用的地方不在内容上,在后面要讲的权限位上。
2.3 权限位写着作者不允许做的事
所有者密码在文件里留下的最要紧的痕迹就是加密字典里那个 P。P 是一个整数。每一位代表一项权限,位上是 1 表示允许、是 0 表示不允许。我的样本里一共出现了四个不同的 P 值,用 PyMuPDF 读出来再对照规范后各自的意思如下。
| P 值 | 用在哪几份 | 禁了什么 |
|---|---|---|
| -3392 | S1、S2、S5 | 打印、高质量打印、修改、复制、注释、填表、组合,只留无障碍提取 |
| -20 | S3 | 只禁复制 |
| -2056 | S4 | 禁打印和高质量打印 |
| 0 | S6 | 全部禁掉,连无障碍提取也禁 |
一个表示权限的整数为什么会是负数,我一开始也看不懂。查了规范才知道 P 是按有符号整数存的,高位上那些保留位规定要写成 1,整个数读出来就成了负的。不同的位怎么组合成这几个具体的数我这次没有一位一位拆,表里「禁了什么」是按库报出来的结果整理的。学到这一层我觉得先记住 P 是作者写给阅读器的一张清单就够了,上面列着作者不希望你做的那些事。这张清单本身没有任何强制力。它不加密任何东西,只是在文件里写着「请不要打印」「请不要复制」。阅读器读到这个数以后可以照办也可以不看,到底照不照办是我在第三章里一个个试出来的。
三、PDF不能复制不能打印是谁在拦
3.1 Chromium拦住了复制和打印
复制我是在正文上从开头那行拖到第四行再按 Cmd+C,然后去另一个页面的文本框里粘贴。每次测之前我都先把剪贴板清空或者写进一串自己认得的哨兵字符串,这样就不会把上一次的结果当成这一次的。下面这张图是 Chromium 149 里两份文件的对照。左边是不加密的那份,右边是一份只设权限密码、禁止复制的文件。

这张图要分上下两半看。上半是阅读器里的拖选,左右两边的「甲方」「乙方」两行、下面两条条款和最后那行英文都盖着一样的蓝色高亮。这说明文字层完好并且选区也正常。下半是粘贴结果,左边文本框里出来的是原文。最后一行停在「permission t」上是因为我那次的拖选刚好停在 tests 的首字母后面。右边的文本框里还是灰色的「粘贴到这里」,红框圈住的就是这个空框。复制不出来并不是文字丢了,是阅读器看到权限位以后没往剪贴板里写东西。
打印也被 Chromium 拦住了,打印按钮在工具栏上照常显示也看不出任何差别。允许打印的 S0 和 S3 点下去就进入了打印流程。禁打印的 S1、S2、S4、S6 点下去什么反应都没有,既没有提示也没有报错。整理 7 份样本的复制结果可以看到禁复制的 S1、S2、S3、S5、S6 粘贴出来的都是空或者哨兵,S0 和只禁打印的 S4 粘贴出来的是正文。拦打印和拦复制都是按各自的权限位来的,跟文件用了什么算法没有关系。这一点第四章再说。
3.2 Chromium没有拦绘制
同一个 Chromium 在编辑这一项上却没管。它的阅读器工具栏上有一个能在页面上画笔迹的「绘制」按钮。7 份样本我都点了。每一份都能进入绘制模式并画出笔迹,撤销按钮也跟着变成了可用。这里面包括声明了禁止修改和禁止注释的 S1、S2、S5、S6。我得说清楚这一项测到了哪一步,我只看了能不能进入绘制、能不能画出来。画完以后能不能把带笔迹的文件存下来我没有测也不打算测。这里只能说 Chromium 的绘制没有被权限位拦住,不能说它能修改并保存受限的文件。同一个阅读器对复制管得很严却对绘制完全不管,我一开始觉得这很矛盾。后来想想它大概只是没把绘制算进「注释」这一项。这只是我的猜测,我没找到 Chromium 对这件事的说明。
3.3 Firefox默认设置下三样都不拦
换到 Firefox 151 自带的阅读器以后默认设置下的结果让我愣了一下。7 份样本的打印按钮都在并且点下去都调起了打印,7 份也都能复制出正文。高亮、文字、绘图这些编辑按钮全部可用并能进入编辑模式。同一份全禁的 S1 在 Chromium 里复制和打印都被拦住了,到了 Firefox 的默认设置下一样都没拦。我写这个结果不是为了比哪个阅读器好用,是想说明权限位能不能起作用完全取决于打开它的那个软件。同一份文件和同样的权限位换一个阅读器就是另一个结果。作者以为自己禁掉了复制,可收件人那边到底能不能复制作者其实是不知道的。
3.4 打开权限执行开关后复制仍然没拦
Firefox 的阅读器有一个叫 pdfjs.enablePermissions 的设置项并且默认是关着的。打开以后阅读器会去读权限位并执行其中一部分限制。我把它打开又把 7 份样本过了一遍,打印被拦住了。禁打印的 S1、S2、S4、S5、S6 工具栏上的打印按钮被隐藏起来,点击也不会调起打印。允许打印的 S0 和 S3 照常可用。编辑也被拦住了。禁修改的 S1、S2、S5、S6 的高亮、文字、绘图这些按钮都变灰不能点,只禁打印的 S4 照常可用。
复制却还是没拦,只禁复制的 S3 和全禁的 S1 都照样能复制出正文。文字层的样式是允许选中的 user-select: auto,我反复测了几遍结果都一样。这个开关拦打印也拦编辑偏偏不拦复制,为什么会这样我没有在设置说明里找到解释。这是这次测试里最出乎我意料的一个结果。下面把三种情况放在一起看。
| 处理方 | 打印 | 复制 | 编辑 / 批注 |
|---|---|---|---|
| Chromium 149 阅读器 | 拦住 | 拦住 | 没拦 |
| Firefox 151 默认设置 | 没拦 | 没拦 | 没拦 |
| Firefox 151 开权限执行 | 拦住 | 没拦 | 拦住 |
三行里没有一行完全相同。权限位要真是一把锁这张表就该三行全是「拦住」。它更像作者递给阅读器的一张请求单,每个阅读器挑着照办了一部分。我不觉得权限密码没用,Chromium 确实把复制和打印拦住了。可我也不会再把它当成一种可靠的保护。
3.5 库只把权限位交给调用方
阅读器是成品软件,执不执行由它自己决定。库就更直接了。它们只负责把权限位报出来,用不用由调用它的程序自己定。pdf.js 6.3 当页面库用时有一个读权限的接口。S1 返回的是一组权限,页面照常渲染并不会因为禁打印就不画。PyMuPDF 1.26 的 permissions 属性返回的是 -3392 这样的整数,读文字的接口也照常工作。这里有个容易踩的小差别。Firefox 内置的 pdf.js 5.7.195 读权限时返回的是数组,我在页面里用的 pdf.js 6.3.289 返回的却是 Set。按数组的写法去查「里面有没有打印」在 6.3 上会直接报错,因为 Set 没有数组那个查找方法。用 pdf.js 读权限的话最好先打印一下返回值的类型再写判断。
库的这种设计我能理解,它们是给别的程序用的零件。读出权限位是它们的本分,执行权限是用它们做成的那个应用的事。前面几节的结果这么乱也是因为每个阅读器底下的库都只负责报告,执行全靠上层自己写。
四、PDF加密算法越强限制越严吗
4.1 四种算法拦下的操作完全一样
样本里我特意放了 AES-256、RC4-128、AES-128 和 RC4-40 四种算法。我原本的预期很朴素。我觉得算法越新越强阅读器就会拦得越严,最老的 RC4-40 说不定什么都拦不住。结果完全不是这样。S1、S2、S6 三份的权限位都是全禁,它们在 Chromium 里和 Firefox 的两种设置下逐项表现相同。用 AES-128 的 S3 和用 AES-256 的 S4 各自按自己的权限位表现。S3 只拦复制,S4 只拦打印。
这里面最能说明问题的是 S1 和 S4。两份都是 AES-256,一份全禁一份只禁打印。在 Chromium 里 S1 复制出来是空的,S4 复制出来却是正文。算法一样结果不一样是因为权限位不一样。S1 和 S6 正好反过来。算法一个最新一个最老,权限位都是全禁,结果一模一样。看到这两组对照我才算彻底放下了「算法越强越严」的想法。道理回到第二章就说得通了。只设权限密码时用户密码是空的,任何算法在空密码面前都一样形同虚设。决定阅读器拦什么的只剩下 P 那个整数和阅读器自己的选择。算法的强弱要等到设了打开密码、密码又不为空的时候才派得上用场。
4.2 输入打开密码以后限制照旧
还剩一个问题我一开始想当然了。S5 设了打开密码。我以为知道打开密码的人就是被授权的人,输对密码以后应该什么都能做。我在 Chromium 里给 S5 输入了用户密码以后文件顺利打开了。接着试复制粘贴出来是空的,再试打印也点了没反应。Firefox 两种设置下 S5 打开以后的表现也跟 S1 相同。用户密码只管能不能打开。打开以后能不能打印、能不能复制还是照着 P 来,跟没设打开密码的 S1 一样。这么一看两种密码其实是两层东西。外面一层是决定你能不能看到内容的打开密码。里面一层是权限位,你看到内容以后阅读器再按它决定让不让你做别的事。两层可以单独设也可以叠在一起。
五、压缩工具为什么拒绝加密的PDF
5.1 两种加密文件压缩时都报失败
测完阅读器我想看看处理 PDF 的工具遇到这两种文件会怎么做。我的项目里也要处理别人传进来的 PDF,正好参考一下别人是怎么定的。试压缩时我用的是图映(https://imging.cn/)的 PDF 压缩,试的是自己生成的只设权限密码和带打开密码两种测试 PDF。具体是 S0、S1、S2、S3、S5 五份并且每份都选了电子书档。页面左栏一直写着一句说明「可以一次拖入多个 PDF。解析、内嵌图重编码和写出全部在本机完成,文件不上传。加密或有权限保护的 PDF 需要先解除保护。」
导入以后五份都显示待处理,S0 是 66 KB,S1 是 67 KB。这一步没有任何提示。带打开密码的 S5 也没有弹出让人输密码的框。点了开始压缩以后 S0 从 66 KB 压到了 49 KB,队列里写着「−25.7%」。其余四份不管是 AES 还是 RC4、全禁还是只禁复制在队列里都标着「失败」并跟着同一句提示「这份 PDF 有加密或权限保护,需要先解除保护」。整个过程我数了一共 46 个请求,其中非 GET 请求是 0 个,也只连了 imging.cn 这一个域名。
这里有一个细节值得记一下。S3 只禁了复制并允许修改和组合页面,照理说压缩这种改动文件的操作作者是允许的。它照样被拒了。这说明压缩工具没去看权限位,只要文件里有加密字典它就一律不处理,也不区分是打开密码还是权限密码。失败以后右侧的进度卡上写着「全部完成 · DONE」和「0 个文件已压缩完成,可以逐个保存。」。四份失败和一份成功放在一起时进度卡这么写我一开始没看懂。我理解它是在说这一批跑完了并不是说每份都成功了,不过两句话摆在一起确实容易让人误会。
5.2 拒绝的原因界面上没有写
图映为什么一律拒绝界面上没有解释,我只能照原话记下那句提示。下面是我自己没有验证过的推测。压缩要把文件拆开再重新写一遍。只设权限密码的文件内容能用空密码解开。可写回去的时候要不要重新加密、要不要保留作者设的那些限制都得做取舍。处理不好的话要么把作者的限制弄丢,要么写出来的文件有问题。直接拒绝并让用户先去找作者可能是最不容易出错的做法,不过这只是我的猜测。从使用者这边看结果很清楚,压缩工具遇到这两种文件都不会帮你处理。提示里说的「先解除保护」落到实处就是去找有权解除的人,也就是文件的作者。下一章讲的就是这件事。
六、PDF已加密不能编辑该找谁
6.1 先看打开时有没有密码框
弄清楚上面这些以后我给自己记了一个判断顺序。收到一份被说成已加密的 PDF 时先别管它用的是 AES 还是 RC4,也别管工具报的是 True 还是 False。直接拿阅读器打开它看看有没有弹出要密码的框。弹了就说明设了打开密码并且内容真的锁着。没弹的话正文能看。这时候复制不出来、打印没反应或者有工具说它加密了,那就是只设了权限密码。这一步不用装任何东西也不用懂权限位。两种情况的处理办法不一样,先分清楚再往下走。设了打开密码的就去找发文件的人要打开密码,没有别的路可走。下面两节说的都是只设权限密码的情况。
6.2 只有权限密码就找原作者重发
只设权限密码的文件里,作者写明了不希望它被复制、打印或编辑。这时候最直接的办法是去找原作者请对方重新导出一份不带限制的版本,或者直接要一份原来的 Word 或者表格这类可编辑的源文件。我觉得开口时把用途说具体一点比较好,比如是要引用其中几段、要打印出来签字还是要改其中的条款。对方知道了用途才好判断该给哪个版本。这条路看起来很笨可只有这一条不用猜。权限限制是作者设的。能决定去不去掉它的也只有作者。第三章里各个阅读器拦得五花八门,第五章里压缩工具直接拒绝。绕来绕去最后都回到了这一步。
6.3 自己的文件用原软件重新导出
还有一种情况是文件本来就是自己做的,比如以前导出 PDF 时随手勾了「禁止复制」,现在自己又要用。这时候回到当初生成它的那个软件里输入当时设的所有者密码,再重新导出一份不带限制的版本就行。所有者密码要是忘了就回到源文件重新导出一次,源文件里是没有这些限制的。我自己的样本也是这么处理的。要改哪一份就改生成它的脚本重新生成一遍,已经生成好的 PDF 我一份都没去动过。
七、适用边界与风险
所有结论都出自 7 份我自己生成的 2 页占位文件。真实的合同、报告和银行回单是用各种软件生成的,它们的权限位怎么设、用哪种算法的分布跟我的样本不会一样。我只能说同样的权限位在这几个阅读器里会有这样的表现,不能说你手上那份文件一定也是这样。阅读器我只测了 Chromium 149 的开源构建和 Firefox 151,库只测了 pdf.js 6.3 和 PyMuPDF 1.26。WebKit 26.5 只测了 pdf.js 页面库并且结果跟另外两个内核相同,WebKit 自己的 PDF 显示没有测。Acrobat Reader、macOS 预览、Safari 正式版、WPS 和手机阅读器都没测。很多人日常用的恰恰是这几个,它们拦不拦、拦哪几项我现在说不上来。浏览器升级以后阅读器的执行方式也可能会变。
风险有两面。对发文件的人来说权限密码不是一把可靠的锁,第三章那张表说明同一份文件在不同软件里的表现可以完全不同。真正不想让别人看到的内容只能靠打开密码或者干脆不发。对收文件的人来说能打开不等于能随便用,作者设了限制就是明确表达了意愿。正规的做法是去问作者,不是找办法绕开它。还有一个风险出在判断这一步。只看一个布尔值会判错。pypdf 和 PyMuPDF 的 is_encrypted 对同一份文件给出了相反的答案。项目里要是拿其中一个做判断就可能把加密文件当成普通文件送进后面的处理流程。我后来都是分开问有没有加密字典和要不要密码这两件事。
八、测量方法的坑
头一个坑是剪贴板。复制测试要是不先清空剪贴板上一次复制成功的正文就会留在里面。下一份文件明明没复制出东西,粘贴时却会粘出上一次的内容。看起来就像复制成功了。我每次测之前都先清空或者写进一串哨兵字符串,粘贴出哨兵就说明这一次什么都没写进去。第二个坑是选区高亮。拖选时出现蓝色高亮很容易让人以为复制没问题。图里左右两边的高亮一模一样可复制结果完全不同。高亮只说明文字层在。能不能复制要看粘贴出来的是什么。
第三个坑是 Chromium 的打印判定。我没有看到真正的打印对话框。在我用的这个构建里允许打印的 S0 和 S3 点下去以后会进入打印流程,接着页面进程就退出了。禁打印的那几份点下去页面没有任何变化。我是靠这个差别判断拦没拦的,这是一个间接信号。Cmd+P 快捷键和打印预览里的行为我都没测。第四个坑是编辑的判定。Chromium 我只看了能不能进入绘制、能不能画出笔迹。Firefox 我只看了编辑按钮能不能点、能不能进入编辑模式。两边都没有保存文件。「没拦编辑」只说到能进入编辑这一步为止。
第五个坑是界面语言。我这台机器上的 Firefox 只有英文界面,Firefox 那几句原话也就都是英文的。Chromium 阅读器的界面固定是中文并且换不成英文。文中引用的原话分别来自这两个界面,换了语言的版本措辞会不一样。最后是文件大小的口径。正文里说每份样本 66 到 69 KB,压缩工具界面上显示 S0 是 66 KB、S1 是 67 KB。两边都是脚本或界面直接给出的数,我没有换算过。拿去跟别的数据对照时要留意它们可能不是同一个口径。
九、还没解决的
最大的一个问题我到现在也没想明白。只设权限密码的文件用户密码是空的,谁都能解开内容。那为什么还要认真用 AES 把内容流加密一遍?加密了跟没加密差不多还白白多了一道计算。我猜是规范要求权限位必须和加密绑在一起,P 要参与密钥计算,没有加密就没法防止别人改掉 P。可这只是猜测。我没读到规范里对应的那一段,也没有验证过。
第二个是 S6 的 P=0 连无障碍提取也禁了,按字面意思读屏软件可能读不出这份文件的文字。我没拿读屏软件试过也就说不上它实际拦住了什么。这件事对视障用户影响很大,我想以后单独测一次。第三个是 Firefox 打开权限执行开关以后仍然不拦复制。它拦打印也拦编辑,偏偏放过复制,我在设置说明里没有找到原因。Acrobat、macOS 预览和 Safari 正式版的表现也还是空白。
如果你手上也有一份「打开不要密码却说已加密」的 PDF,可以先拿阅读器打开看看有没有密码框。没有的话就对照第二章那张 P 值表看看库报出来的权限位禁了哪几项,然后再去找原作者要一份不带限制的版本。
参考资料
- ISO 32000-2(PDF 2.0):加密一节,Standard 安全处理器、/Encrypt 字典与用户访问权限 P 的定义
- pdf.js API 文档:getDocument 的 onPassword 回调、getMetadata、getPermissions
- pdf.js 的 enablePermissions 选项说明(Firefox 中对应 pdfjs.enablePermissions)
- PyMuPDF 文档:Document.needs_pass、is_encrypted、permissions、authenticate、metadata
- pypdf 文档:PdfReader.is_encrypted
- 本文实测数据:2026-09-30 本机运行记录,环境见「版本声明」