如何在 Python 中使用 PDF 加密引擎

如何在 Python 中使用 PDF 加密引擎

Aspose.PDF FOSS for Python(import aspose_pdf)在字节层面通过 aspose_pdf.engine.encryption 中的一个小型内部引擎类 EncryptionUtils 实现 PDF 密码保护。这里正是 AES-CBC 密码以及 PDF 标准安全处理器的密钥派生算法(ISO 32000 第 2 至第 6 版)所在的位置,且独立于高级的 Document API。本指南将安装该包并演示三个引擎任务:使用 AES-CBC 对原始字节进行加密和解密、为密码(第 2-4 版)派生标准安全处理器密钥,以及为第 5/6 版文档派生 AES-256 密钥。

分步指南

步骤 2:导入所需类

EncryptionUtils 未从顶层 aspose_pdf 包重新导出 — 请直接从 engine.encryption 子模块导入:

import os
from aspose_pdf.engine.encryption import EncryptionUtils

步骤 3:使用 AES-CBC 加密和解密数据

encrypt_aes_cbc 和 decrypt_aes_cbc 可直接使用任意 16、24 或 32 字节的密钥(AES-128、AES-192 或 AES-256),无需经过基于密码的密钥派生。encrypt_aes_cbc 在内部应用 PKCS7 填充,而 decrypt_aes_cbc 则将其移除。

key = os.urandom(32)  # AES-256; 16- and 24-byte keys are also accepted
plaintext = b"Confidential PDF content"

ciphertext = EncryptionUtils.encrypt_aes_cbc(key, plaintext)
decrypted = EncryptionUtils.decrypt_aes_cbc(key, ciphertext)

assert decrypted == plaintext
print("Roundtrip OK, ciphertext length:", len(ciphertext))

步骤 4:为密码派生标准安全处理程序密钥(修订版 2-4)

compute_owner_key_v4 根据算法 3.3 计算所有者密码值 (O),compute_user_key_v4 根据算法 3.4/3.5 计算用户密码值 (U) 和文件加密密钥。verify_password_v4 从候选密码重新派生密钥,以便您可以在信任之前确认它能打开文档。

owner_password = "owner-secret"
user_password = "user-secret"
key_length = 16   # 128-bit (AES/RC4); Revision 2 uses 5 (40-bit)
revision = 4
file_id = os.urandom(16)
permissions = -4  # standard PDF permissions value

o_value = EncryptionUtils.compute_owner_key_v4(
    owner_password, user_password, key_length, revision
)

u_value, encryption_key = EncryptionUtils.compute_user_key_v4(
    user_password, o_value, permissions, file_id, key_length, revision
)

verified_key = EncryptionUtils.verify_password_v4(
    user_password, u_value, o_value, permissions, file_id, key_length, revision
)
assert verified_key == encryption_key

ciphertext = EncryptionUtils.encrypt_aes_cbc(verified_key, b"Confidential PDF content")
decrypted = EncryptionUtils.decrypt_aes_cbc(verified_key, ciphertext)
assert decrypted == b"Confidential PDF content"

o_value 和 u_value 始终为 32 字节,无论 key_length 为何。verify_password_v4 在密码不匹配时返回 None —— 这不是异常。


步骤 5:为修订版 5/6 文档派生 AES-256 密钥

修订版 5 和 6 的安全处理程序(AES-256)不使用 compute_owner_key_v4/compute_user_key_v4 —— 它们使用 compute_hash_v5 对密码进行哈希,该实现符合 ISO 32000-2 的 “Algorithm 2.B”。使用 generate_file_id 获取全新的文件标识符,并使用 derive_object_key 将文件加密密钥转换为特定间接对象的密钥。

password = b"SecurePassword123"
salt = os.urandom(8)

password_hash = EncryptionUtils.compute_hash_v5(password, salt)
assert len(password_hash) == 32

file_id = EncryptionUtils.generate_file_id()
assert len(file_id) == 16

file_key = os.urandom(32)
object_key = EncryptionUtils.derive_object_key(file_key, 12, 0, use_aes=True)
assert len(object_key) == 16

derive_object_key 将对象号和代号混入文件密钥中,因此每个间接对象都有自己的密钥,尽管文件密钥是共享的。


常见问题及解决方案

encrypt_aes_cbc/decrypt_aes_cbc 抛出 “AES 密钥必须为 16、24 或 32 字节”。 密钥参数必须是实际的 AES 密钥,而不是原始密码字符串。使用以下方式生成: os.urandom(16 | 24 | 32),或者先使用以下方式派生: compute_user_key_v4 (修订 2-4)或 compute_hash_v5 (修订 5/6)。

verify_password_v4 returns None 而不是抛出。 这意味着密码不匹配——这不是需要捕获的错误情况,而是一个需要检查的结果。同时确认 permissions, file_id, key_length,并 revision 与文档在…时使用的完全相同的值 o_value and u_value 最初被计算的;其中任意一个不匹配时会悄悄产生错误的密钥,而不是明确报错。

compute_owner_key_v4 使用空的所有者密码。 Passing "" for owner_password 是规范定义的行为,而非 bug —— 算法会回退使用用户密码,因此所有者密码和用户密码等价。

为错误的修订版或密钥长度派生的密钥。 修订版 2 使用 40 位(5 字节)RC4 密钥,修订版 3/4 使用 128 位(16 字节)RC4/AES 密钥,而修订版 5/6 使用 compute_hash_v5 而不是 compute_owner_key_v4/compute_user_key_v4 完全如此。混合使用这些不会抛出异常 —— 只会生成一个无法打开文档的密钥。

compute_hash_v5 输出意外改变。 第三个参数(user_key)被混入哈希中。传入 b"" 显式传入,当你想要一个纯粹的密码验证哈希时,因为省略它并不等同于在每个调用点传入空值。


常见问题

为什么不能直接从 aspose_pdf 导入 EncryptionUtils?

它位于 engine 子包中,因为它是文档级密码保护的内部实现细节,并不属于高层公共的 API 接口。请从 aspose_pdf.engine.encryption 显式导入它。

compute_hash_v5 与单次 SHA-256 调用相同吗?

不。它实现了 ISO 32000-2 的算法 2.B,该算法使用不同的哈希算法进行多轮运算,算法由中间值选择——故意比单次哈希调用更耗费资源。

generate_file_id() 每次都会返回相同的字节吗?

不——它在每次调用时返回一个全新的、随机的 16 字节值。不同的文件标识符会导致从中派生的每个密钥都发生变化,即使密码相同。

加密的 PDF 中的所有间接对象是否共享同一个密钥?

不。derive_object_key 将对象编号和代数与文件加密密钥结合,因此每个间接对象都会获得唯一的对象级密钥。

应用代码是否应直接调用 EncryptionUtils 来对 PDF 设置密码保护?

通常不建议。EncryptionUtils 是文档级密码 API 所基于的底层引擎。大多数应用应通过文档级 API 来保护 PDF,仅在需要自定义工具或对加密文件进行取证检查时才使用 EncryptionUtils。


另请参阅

 中文