如何在 Python 中添加 PDF 安全签名
Aspose.PDF FOSS for Python 包含一个签名妥协检测器,它会检查已签名的 PDF 是否存在特定的篡改模式:在签名的已签字节范围之后追加有意义的字节,这是增量更新的 PDF 文件独有的风险。本指南使用 SignaturesCompromiseDetector 和 CompromiseCheckResult 类将此检查添加到 Python 工作流中。创建或应用新的数字签名是库的另一部分,本文不涉及。可以使用以下命令安装该库。
分步指南
步骤 1:安装软件包
安装 Aspose.PDF FOSS 软件包:
git clone https://github.com/aspose-pdf-foss/Aspose-PDF-FOSS-for-Python.git
cd Aspose-PDF-FOSS-for-Python
pip install -e .通过导入 SignaturesCompromiseDetector 并打印确认信息来验证安装:
from aspose_pdf import SignaturesCompromiseDetector
print("aspose-pdf-foss-for-python is ready.")第2步:导入所需类
from aspose_pdf import CompromiseCheckResult, SignaturesCompromiseDetector第3步:准备一个公开签名列表的对象
SignaturesCompromiseDetector 本身并不解析 PDF —— 它的构造函数接受任何拥有 signatures 属性的对象:一个签名条目列表,每个条目包含 valid、byte_range 和 reference_data。PdfSignature —— 一个普通的、可直接实例化的类 —— 符合该结构,因此在将其连接到您自己的文档对象之前,用于查看检查的行为非常有用:
from types import SimpleNamespace
from aspose_pdf import PdfSignature
with open("signed.pdf", "rb") as handle:
pdf_bytes = handle.read()
signature = PdfSignature(
name="Signature1",
contents=b"...", # PKCS#7 signed-data blob from the signature dictionary
byte_range=[0, 1024, 1040, len(pdf_bytes) - 1040],
reference_data=pdf_bytes,
)
signed_document = SimpleNamespace(signatures=[signature])一旦您的文档对象公开了相同结构的 signatures 列表,就用您自己的文档对象替换 signed_document —— 任何具有该属性的对象都可工作。
第4步:运行妥协检查
from aspose_pdf import SignaturesCompromiseDetector
detector = SignaturesCompromiseDetector(signed_document)
result = detector.check()
print(result.compromised) # bool
print(result.has_compromised_signatures) # bool -- same value as compromised
print(result.signatures_coverage) # int -- number of signatures inspected
print(result.reasons) # list[str] -- human-readable findingscheck() 遍历 signed_document.signatures 中的每个签名,查找在每个签名的已签字节范围之后追加的未签名内容。CompromiseCheckResult 对结果进行汇总:compromised 和 has_compromised_signatures 以两个不同的名称报告相同的布尔值,signatures_coverage 报告检查了多少个签名,reasons 列出每个发现问题的人类可读解释。
第5步:处理没有签名的文档
空的或缺失的 signatures 列表并不是错误状态 —— 它的报告方式与没有任何可检查内容的文档相同:
from types import SimpleNamespace
from aspose_pdf import SignaturesCompromiseDetector
unsigned_document = SimpleNamespace(signatures=[])
detector = SignaturesCompromiseDetector(unsigned_document)
result = detector.check()
print(result.compromised) # False
print(result.reasons) # ["unsigned document"]传递 SignaturesCompromiseDetector(None) — 构造函数的默认值 — 的行为相同。
常见问题与解决方案
compromised is False 针对已知在签名后被编辑的 PDF
SignaturesCompromiseDetector 专门查找签名的已签字节范围之后追加的未签名字节 —— 这是一种典型的天真增量更新篡改模式。它不会执行完整的加密签名验证。为此,请对单个 PdfSignature 对象调用 validate()。
compromised and has_compromised_signatures 似乎多余
它们的值相同:compromised 是返回 has_compromised_signatures 的计算属性。根据代码的可读性选择使用任意一个名称即可。
当文档没有时不会抛出异常 signatures 任何属性
SignaturesCompromiseDetector 将没有 signatures 属性或 document=None 的文档视为未签名文档——check() 返回包含 has_compromised_signatures=False 和 reasons=["unsigned document"] 的结果,而不是抛出异常。
格式错误的 PdfSignature 不会被标记
check() 会跳过任何其 byte_range 不是四元素列表,或其 reference_data 不是 bytes/bytearray 的签名,而不是抛出异常或将其报告为受损。形状错误的签名会被静默地从检查中排除,而不会被标记。
将此检测器与签名创建混淆
SignaturesCompromiseDetector 和 CompromiseCheckResult 只检查文档中已存在的签名——它们没有创建、应用或嵌入新签名的方法。
常见问题
这里到底什么情况算作“受损”?
在签名的已签字节范围之后追加到 PDF 中的有意义的、非空白字节——检测器已经考虑了两种例外情况:后续的签名或时间戳本身覆盖了这些字节,以及仅添加验证材料(例如 /DSS)而不叠加新内容(如批注)的增量更新。
这是否验证签名本身的加密有效性?
不。SignaturesCompromiseDetector 检查签名字节范围周围的篡改模式。加密有效性是一个单独的检查,可通过 PdfSignature.validate() 获得。
signatures_coverage 告诉我什么?
检查实际在传入的文档上检查的签名数量——这对于在信任干净结果之前确认检测器看到了您期望的签名非常有用。
我能一次性检查包含多个签名的文档吗?
是的。check() 遍历 signed_document.signatures 中的每个条目,并返回一个覆盖所有条目的聚合 CompromiseCheckResult。
在正常使用中,我需要自行构造 PdfSignature 对象吗?
不——第 3 步的 PdfSignature 构造用于直接探索检测器的行为。在真实工作流中,只需传入文档加载代码已经生成的对象,只要它提供一个 signatures 列表,包含 PdfSignature 形状的条目即可。