本工具在浏览器本地完成 PDF 权限限制(Owner 密码)的移除,文件不上传服务器。
unlock_core.js 的实现,逐层说明解密原理。PDF 的安全机制由 标准安全处理器(Standard Security Handler) 实现。加密信息集中存放在文件内的 加密字典(Encryption Dictionary) 中,由 trailer 的 /Encrypt <对象号> 0 R 引用。
加密只影响两类内容:
/StmF 指定使用的密码过滤器)/StrF 指定)解密需要解决两个问题:
下表为「PDF 版本 → 加密算法 → 密钥长度 → 加密字典字段」的对应关系:
| PDF 版本(兼容性) | 加密算法 | 密钥长度 | 加密字典对应 |
|---|---|---|---|
| PDF 1.1 / 1.2 / 1.3 | RC4 | 40 位 | /V 1 /R 2 /Length 40(无 /CF) |
| PDF 1.4 / 1.5 | RC4 | 128 位 | /V 2 /R 3 /Length 128;或 /V 4 /CFM /V2 /Length 128 |
| PDF 1.6 / 1.7 | AES(AES-CBC) | 128 位 | /V 4 /CFM /AESV2 /Length 128 |
| PDF 1.7 及以后(低安全性) | AES | 256 位 | /V 5 /R 5 /CFM /AESV3 /Length 256(单次 SHA-256) |
| PDF 1.7 及以后(高安全性) | AES(AES-256 + PBKDF2) | 256 位 | /V 5 /R 6 /CFM /AESV3 /Length 256(迭代哈希) |
/V(算法版本)决定密钥派生与加密算法族;/R(修订号)决定密码验证细节;Length 40),RC4-128 为 16 字节(Length 128);| 字段 | 含义 | 示例 |
|---|---|---|
/Filter /Standard | 标准安全处理器 | /Filter /Standard |
/V | 加密算法版本(1–5) | /V 5 |
/R | 算法修订号(2–6) | /R 6 |
/Length | 密钥长度(位,经典加密时) | /Length 256 |
/O | Owner 密码相关(32B,V<5;48B,V=5) | /O <e67f...> |
/U | User 密码相关(32B,V<5;48B,V=5) | /U <542c...> |
/OE / /UE | V5:Owner/User 文件密钥的 AES 加密版本(32B) | /UE <7d3a...> |
/P | 权限位(32 位掩码,含权限标志与三位固定位) | /P -3904 |
/CF /StdCF /CFM | 密码过滤器:/V2(RC4)、/AESV2(AES-128)、/AESV3(AES-256) | /CFM /AESV3 |
/StmF / /StrF | 流/字符串使用的过滤器(默认 /Identity 即不加密) | /StmF /StdCF |
/EncryptMetadata | 是否加密元数据(AES-128 时影响文件密钥派生) | /EncryptMetadata false |
/Encrypt,解析 /V /R /Length /O /U /P /CFM /StmF /StrF /UEMD5(pw+O+P+ID0),R≥3 再迭代 50 次);V5:算法 2.B(R5 = 一次 SHA-256,R6 = AES-CBC 迭代哈希)/U;V5:算法 11 校验 /U 并解密 /UE,得到 32 字节文件密钥MD5(fileKey[:n]+salt)[:n+5](AES-128 再追加 sAlT);V=5 直接用 32B 文件密钥/Encrypt,按解密长度平移偏移并修正 xref → 下载无权限限制的 PDF从 trailer 找到 /Encrypt <N> 0 R,按对象偏移读取加密字典文本,正则提取:
/V、/R、/CFM(/V2 → RC4、/AESV2 → AES-128、/AESV3 → AES-256)/Length、/P、/O、/U、/UE(V5 时)/StrF(缺失或 /Identity 时字符串不加密)/O、/U、/UE 同时支持 hex 字符串(<...>)与 括号字符串((...))两种形式,后者需做转义还原(\(、\)、\\、八进制 \ddd 等)。
V1–V4(经典加密,算法 2)
key = MD5( password(空) + O[0:32] + P(4字节小端) + ID[0] ) 对 R≥3:重复 50 次:key = MD5(key + 前3字节) 取前 n 字节(n = Length/8;n 最大 16)
注意:AES-128(V4)且 /EncryptMetadata false 时,输入末尾再追加 4 字节 FF FF FF FF。ID[0] 来自 trailer 的 /ID,参与密钥派生,因此同一文件改字节会导致密钥变化。
V5(算法 2.B)
key = SHA-256( password + U[32:40] + U[40:48] )(一次哈希,快但可暴力破解)hash = SHA-256( password + salt + udata )
count = 0
循环:
count++
k1 = password + hash + udata
e = AES-128-CBC 加密( 密钥=hash[0:16], IV=hash[16:32], 数据=k1 重复 64 次, 无padding )
sel = (e[0]+...+e[15]) % 3 # 决定本轮用哪个哈希
hash = SHA-256 / SHA-384 / SHA-512 ( e )
当 count ≥ 64 且 e 末字节 ≤ count-32 时停止
key = hash[0:32]
R6 的停止条件与轮换哈希使得每次派生要做数千次 AES 块加密与哈希,这正是「高安全性」抗暴力破解的原理,也是解密耗时的主要来源之一。
MD5(空密码+padding+ID[0]),再按字节递增序号做 20 次 RC4 迭代,与 /U 比对。匹配即说明该文件可用空密码打开(本工具处理范围)。U[0:32] == SHA-256( 空密码 + U[32:40] );tmpKey = SHA-256( 空密码 + U[40:48] );这一步同时完成了文件密钥的最终确认(实现中与 pypdf 交叉验证,R5/R6 文件密钥逐字节一致)。
每个对象按 对象号(3字节小端) + 生成号(2字节小端) 生成盐(salt):
| 加密类型 | 对象密钥 |
|---|---|
V=1(RC4-40,无 /CF) | 扩展密钥 = fileKey + salt(规范注 1:V=1 时 MD5 替换为 RC4,直接用扩展密钥作 RC4 密钥);兼容生成器(pypdf 等)另按 MD5(fileKey+salt)[:n+5] 备选 |
| V≥2 RC4 | MD5( fileKey[:n] + salt )[: min(n+5, 16)] |
| V=4 AES-128 | 同上,salt 再追加 4 字节 sAlT |
| V=5 AES-256 | 直接用 32 字节文件密钥(无 MD5、无对象盐,算法 3.1a) |
RC4-40 文件的密钥选择是本工具近期修复的重点:同一派生错误会让所有流解密失败。实现采用「内容验证 + 主/备选密钥自适应」:先用规范密钥解密,若内容校验失败(解出的流不是合法 FlateDecode/图像数据)再试 MD5 备选密钥,并让字符串解密跟随流解密的统一结论。
plain = RC4(key, stream))。因等长无校验,必须做内容验证:解出的字节必须是合法 FlateDecode 头(78 9C 等)或合法图像编码(JPX 需 FF 4F FF 51 头),否则视为未加密流,保留原样。crypto.subtle)。解密后长度缩短,必须按对象号降序解密并逐字节平移重建后续字节与 xref 偏移。只有「经典加密(V<4)」或「加密字典显式 /StrF 非 /Identity」时字符串才加密(否则跳过,这正是某些文件书签完好的原因)。密文有两种形式:
(...):AES 时 IV + 密文 拼接,解密后重转义为安全括号字面量写回;RC4 时等长解密;<...>:同样按上述规则解密,写回为 hex。解密结果必须保持字节长度与文本长度一致,并在替换后继续向后扫描(平移后的偏移自适应)。
/Encrypt 引用;| 加密方法 | V/R | 文件密钥派生 | 对象密钥 | 流解密 | 密码验证 |
|---|---|---|---|---|---|
| RC4-40 | 1/2 | 算法2,5 字节 | 扩展密钥(规范)/ MD5 备选 | RC4 等长 + 内容验证 | 算法6(20 次 RC4 迭代) |
| RC4-128 | 2/3 或 4/CFM V2 | 算法2,16 字节(R3 迭代 50 次) | MD5+salt[:n+5] | RC4 等长 + 内容验证 | 算法6 |
| AES-128 | 4/AESV2 | 算法2 + EncryptMetadata 修正 | MD5+salt+sAlT | AES-CBC PKCS7(Web Crypto) | 算法6 |
| AES-256 低安全 | 5/R5 | 算法2.B:SHA-256 一次 | 文件密钥(32B) | AES-256-CBC(Web Crypto) | 算法11 |
| AES-256 高安全 | 5/R6 | 算法2.B:迭代哈希(AES-CBC + 轮换哈希) | 文件密钥(32B) | AES-256-CBC(Web Crypto) | 算法11 |
注:R6 迭代与 /UE 解密需要无 padding 的 AES-CBC,Web Crypto 不提供,故核心内置了纯 JS AES 块实现(SBOX/密钥扩展/CBC 链接,经 NIST 测试向量验证),这是单文件自包含的关键。
支持
不支持 / 明确拒绝
隐私 全部逻辑运行在浏览器本地 JavaScript,文件不离开本机,无需上传。