PDF能打开却不能复制,原来是权限密码

一份 PDF 双击就开了,正文也清清楚楚。我在正文上拖选 4 行后蓝色高亮照常出来。按 Cmd+C 再去文本框里粘贴却什么也没有。换个工具去读,它又报这份文件已加密。我以前一直觉得加密就得输密码。可这份文件从头到尾没让我输过一次密码,这两件事在我脑子里怎么都对不上。我干脆自己生成了 7 份 2 页的测试 PDF 一份份去试。内容全是占位文字,标题叫「示例合同(占位文字,仅供测试)」。

工具说的加密是真的

7 份里除了 1 份不加密和 1 份设了打开密码的对照,剩下 5 份都只设了权限密码,也就是用户密码留空、只填所有者密码。算法我特意换了 AES-256、AES-128、RC4-128 和 RC4-40 这几种。这 5 份在 Chromium 149 开源构建和 Firefox 151 自带的阅读器里都能直接显示。界面上也看不到任何跟限制有关的提示。可它们确实带着完整的加密字典,内容流也真被加密过。只是用户密码为空时阅读器打开会自动拿空密码把它解开。我先用 Python 测了一下结果被两个库的不同说法绕了一圈。

python 复制代码
from pypdf import PdfReader
import fitz

for f in ["S0-无加密.pdf", "S1-仅权限密码-AES256-全禁.pdf", "S5-打开密码-AES256-全禁.pdf"]:
    lock = fitz.open(f)
    print(f[:2], "| pypdf:", PdfReader(f).is_encrypted,
          "| fitz:", lock.is_encrypted, lock.needs_pass,
          lock.metadata and lock.metadata.get("encryption"))
# S0 | pypdf: False | fitz: False 0 None
# S1 | pypdf: True | fitz: False 0 Standard V5 R6 256-bit AES
# S5 | pypdf: True | fitz: True 1 None

两个库都有一个叫 is_encrypted 的属性,对只设权限密码的 S1 却给出了相反的答案。pypdf 给的 True 问的是文件里有没有加密字典。PyMuPDF 给的 False 问的是文件眼下还锁没锁着,S1 早就被空密码解开了。真正能区分打开密码的是 needs_pass 这一项,三份里只有 S5 是 1。我一开始只看了 PyMuPDF 那个 False,差点在笔记里写成「这份文件没加密」。后来把 metadata 里的加密描述也打出来才看到 S1 写着 AES 256 位。

权限密码靠阅读器配合

权限密码写进文件的其实是一串权限位。它标的是作者允不允许打印、复制、修改和注释这几件事。它本身不负责把内容藏起来,更像是在告诉打开它的软件「作者不希望你这么做」。软件听不听要看软件自己。下面这张图是我测了好几遍的 Chromium 149 阅读器对照。左右两边都能拖出蓝色高亮。左边那份未加密的文件粘贴得到原文。右边只设了权限密码并禁止复制的那份在红框里粘贴出来是空的。

这张图要看的是高亮和粘贴结果之间的落差。选区显示正常说明文字层还在,粘贴为空则说明是阅读器没把文字写进剪贴板。同一个 Chromium 对禁打印的文件也管得住,点打印按钮没有任何反应。它偏偏放行了绘制。7 份文件都能进「绘制」画出笔迹,禁注释的也不例外。Firefox 151 在默认设置下打印、复制、编辑三样都没拦。我把它的权限执行开关打开以后打印和编辑被拦住了,复制还是没拦。pdf.js 和 PyMuPDF 这类库只把权限位报给调用方,拦不拦要看用它的那个应用怎么定。换了几种 AES 和 RC4 算法以后结果也完全一样。真正影响行为的只有权限位。

测到这里我也不好说权限密码没用,毕竟 Chromium 确实拦住了复制和打印。它更像一份请阅读器配合的声明,碰到不配合的软件就管不住。用户密码既然是空的为什么内容流还要认真加密一遍?这个我查了一圈还没想明白。

打开密码才真的锁住内容

设了打开密码的 S5 完全是另一回事。Chromium 一打开就弹出「需要密码」的对话框,框里那行说明是「本文档设置了密码保护,请输入密码。」。换到 Firefox 的英文界面,弹框里写的是「Enter the password to open this PDF file.」。不输密码就一个字也看不到。

这个密码框就是两种密码最直观的区别。只设权限密码的文件根本不会弹出它。还有一点我之前理解错了。我输入用户密码打开 S5 以后 Chromium 照样拦复制、拦打印。用户密码只管这份文件能不能打开,打开以后的限制还是照着权限位来执行。

我还顺手拿这几份文件试了压缩。我用的是图映(imging.cn/)的 PDF 压缩,试的是自己生成的只设权限密码和带打开密码两种测试 PDF。未加密的那份从 66 KB 压到 49 KB 省了 25.7%。加密的几份在队列里一律标着「失败」并提示「这份 PDF 有加密或权限保护,需要先解除保护」。只禁复制、允许修改的那份照样被拒,带打开密码的那份也没有弹出密码框。整个过程 46 条请求里非 GET 请求是 0 条。右边的进度卡倒是同时写着「全部完成」和「0 个文件已压缩完成」,两句放在一起有点对不上。界面没说它为什么拒绝。我猜是重写文件会碰到作者设的那些限制,不过这只是推测。

能打开的文件该找原作者要

弄清楚之后再收到这种文件我会先看它打开时有没有要密码。要了的话,打开密码只能找发文件的人去要。没要密码却不能复制或打印的就是只设了权限密码。这时最省事的办法是请原作者重新导出一份不带限制的版本,或者直接要一份可编辑的源文件。文件要是我自己做的又还记得所有者密码就回到当初生成它的软件里输入所有者密码再重新导出。Acrobat、macOS 预览和 Safari 正式版我都没测。WebKit 26.5 这边我测的只是页面里跑的 pdf.js。这几个阅读器会怎么对待权限位我现在还说不上来。

相关推荐
成旭先生1 小时前
AI 文本审核 API:一段中文文本判风险等级、命中标签与处置建议
java·前端·人工智能·api接口·内容风控·文本审核·ugc审核
liangshanbo12151 小时前
前端面试题:AI 对话中超长消息导致内存溢出,怎么解决?
java·开发语言·前端
律宏阔1 小时前
Dart FFI 内存管理:用 using + Arena 替代嵌套 try-finally
前端·flutter
guslegend1 小时前
脚手架入门:必要性、核心功能与执行原理
前端·架构·node.js·脚手架·前端工程化
律宏阔1 小时前
Flutter 调用 Go:从 c-shared + ffigen 到 @Native + Native Assets 踩坑记录
前端·flutter
LEE2 小时前
前端转型全栈 05:SQL 与迁移,AI 写的 SQL 怎么安全上线
前端·后端·ai编程
用户15741568165342 小时前
macOS 打包体积异常的排查实录
前端
数据掘金2 小时前
小程序埋点方案上线前要检查哪些点?我用一张检查清单过了二十多项
前端
Hooray2 小时前
后台管理框架存活率大调查(2026版)
前端