
数字签名是 PDF 文档安全体系中的核心机制它通过非对称加密技术保证文档的完整性、真实性和不可抵赖性。在合同审批、法律文书归档、电子发票流转等场景中数字签名能够证明文档自签署以来未被篡改并明确签署者身份。然而在实际业务流程中签名操作并非一劳永逸——当证书过期、签署人变更或文档需要重新修订时往往需要移除已有的数字签名并重新签署。通过 Python 编程方式管理 PDF 数字签名可以实现签名添加、验证和移除的自动化特别是在批量签署合同或集成到审批系统时编程方式比手动操作工具更加高效可靠。本文将介绍如何使用 Python 在 PDF 文档中添加可见和不可见数字签名、配置时间戳和文档权限、验证签名有效性以及移除已有的数字签名。环境准备本文使用 Spire.PDF for Python 库来操作 PDF 数字签名。该库提供了PdfOrdinarySignatureMaker和Security_PdfSignature等签名工具类支持 PFX 证书加载、签名外观自定义和时间戳配置。通过以下命令安装pipinstallSpire.PDF安装完成后在脚本中导入所需模块即可开始使用。数字签名需要准备一个 PFX/P12 格式的证书文件含私钥以及对应的密码。添加可见数字签名可见签名会在 PDF 页面上显示一个签名区域包含签署者姓名、位置、联系方式和签名图片等信息。这种签名方式适合需要直观展示签署信息的正式文档。PdfOrdinarySignatureMaker类负责加载证书并生成签名对象PdfSignatureAppearance类则用于控制签名区域的外观呈现。fromspire.pdfimport*inputFile/Sample.pdfinputFile_pfx/sign.pfxinputImage/signature.pngoutputFileDigitalSignature.pdf# 加载 PDF 文档docPdfDocument()doc.LoadFromFile(inputFile)# 使用 PDF 文档和 PFX 证书创建签名生成器signatureMakerPdfOrdinarySignatureMaker(doc,inputFile_pfx,e-iceblue)# 配置签名属性signaturesignatureMaker.Signature signature.NameAlice Chensignature.ContactInfo028-81705109signature.LocationChengdu, Chinasignature.ReasonThe certificate is used to sign the document.# 创建签名外观appearancePdfSignatureAppearance(signature)appearance.NameLabelSigner: appearance.ContactInfoLabelContactInfo: appearance.LocationLabelLocation: appearance.ReasonLabelReason: appearance.SignatureImagePdfImage.FromFile(inputImage)appearance.GraphicModeGraphicMode.SignImageAndSignDetail appearance.SignImageLayoutSignImageLayout.none# 将签名应用到第一页的指定位置signatureMaker.MakeSignature(Signer:,doc.Pages.get_Item(0),90.0,550.0,270.0,90.0,appearance)# 保存文档doc.SaveToFile(outputFile)doc.Close()上述代码中PdfOrdinarySignatureMaker的构造函数接收三个参数PDF 文档对象、PFX 证书路径和证书密码。signature.Name、signature.Location等属性用于填充签名元数据这些信息会嵌入到签名字典中并在签名外观区域显示。PdfSignatureAppearance类控制签名的视觉呈现——GraphicMode.SignImageAndSignDetail表示同时显示签名图片和签署详情SignImageLayout.none表示图片不单独占据一个区域而是与文字信息混合排列。MakeSignature()方法的参数依次为签名名称、目标页面、X 坐标、Y 坐标、宽度和高度签名框将定位在第一页的指定位置。添加不可见数字签名某些场景下不需要在页面上显示签名外观只需在文档内部嵌入数字签名即可。不可见签名不会在页面上产生可见的签名框但仍具有完整的法律效力。这种方式适合批量签署大量文档或在文档布局已定稿、不希望添加额外视觉元素的情况下使用。fromspire.pdfimport*inputFile/Sample.pdfinputFile_pfx/sign.pfxoutputFileInvisibleSignature.pdf# 加载 PDF 文档docPdfDocument()doc.LoadFromFile(inputFile)# 创建签名生成器signatureMakerPdfOrdinarySignatureMaker(doc,inputFile_pfx,e-iceblue)# 生成不可见签名指定签名名称signatureMaker.MakeSignature(signName)# 保存文档doc.SaveToFile(outputFile)doc.Close()与可见签名不同不可见签名调用的是MakeSignature()的单参数重载版本只接收签名名称作为参数不传入页面和坐标信息也不创建PdfSignatureAppearance对象。签名数据仍然完整嵌入到 PDF 的 AcroForm 字段中任何支持数字签名的 PDF 阅读器都可以在签名面板中查看和验证。添加带时间戳和权限控制的签名在对签名时效性要求较高的场景中可以引入可信时间戳Trusted Timestamp来证明签名发生的确切时间。时间戳由可信第三方机构TSA签发即使签名证书后续过期时间戳仍能证明文档在证书有效期内已完成签署。同时还可以通过DocumentPermissions属性限制签署后文档的修改权限例如禁止修改文档内容但允许填写表单。fromspire.pdfimport*inputFile./Data/SampleB_1.pdfinputFile_pfx./Data/gary.pfxinputImage./Data/E-iceblueLogo.pngoutputFileSignedByTimestamp.pdf# 加载 PDF 文档docPdfDocument()doc.LoadFromFile(inputFile)# 创建数字签名对象signatureSecurity_PdfSignature(doc,doc.Pages.get_Item(0),inputFile_pfx,e-iceblue,signature)# 设置签名框位置和尺寸signature.BoundsRectangleF(PointF(90.0,550.0),SizeF(180.0,90.0))# 配置签名详情signature.NameLabelDigitally signed by:Garysignature.LocationInfoLabelLocation:signature.LocationInfoCNsignature.ReasonLabelReason:signature.ReasonEnsure authenticitysignature.ContactInfoLabelContact Number: signature.ContactInfo028-81705109# 设置文档权限允许填写表单但禁止修改内容signature.DocumentPermissionsPdfCertificationFlags.AllowFormFill.value|PdfCertificationFlags.ForbidChanges.value# 设置签名图片signature.GraphicsModeSecurity_GraphicMode.SignImageAndSignDetail signature.SignImageSourcePdfImage.FromFile(inputImage)# 配置时间戳服务器 URLurlhttps://freetsa.org/tsrsignature.ConfigureTimestamp(url)# 保存文档doc.SaveToFile(outputFile)doc.Close()这段代码使用了Security_PdfSignature类与前面的PdfOrdinarySignatureMaker是两种不同的签名 API 风格它直接在构造函数中传入文档、页面、证书路径、密码和签名名称。DocumentPermissions通过位运算组合多个权限标志——AllowFormFill允许用户填写表单字段ForbidChanges禁止对文档内容进行任何修改。ConfigureTimestamp(url)方法接收一个 TSA 服务器地址签名过程中会自动向该服务器发送请求获取时间戳令牌并嵌入签名中。使用|运算符可以组合多个权限标志这与 C# 中的枚举位运算法则一致。验证签名有效性和文档完整性添加签名后在后续流程中通常需要验证签名的有效性和文档是否被修改。签名验证包括检查证书链是否可信、签名哈希是否匹配等。通过PdfFormWidget可以访问 PDF 中的表单字段签名字段是其中的一种特殊类型通过PdfSignatureFieldWidget进行操作。fromspire.pdfimport*inputFileDigitalSignature.pdf.pdfoutputFileVerifySignature.txt# 加载已签名的 PDF 文档docPdfDocument()doc.LoadFromFile(inputFile)# 获取文档中的表单pdfformdoc.Form formWidgetPdfFormWidget(pdfform)texts[]# 遍历所有表单字段ifformWidget.FieldsWidget.Count0:foriinrange(formWidget.FieldsWidget.Count):fieldformWidget.FieldsWidget.get_Item(i)ifisinstance(field,PdfField):# 将字段转换为签名字段signatureFieldPdfSignatureFieldWidget(field)fullNamesignatureField.FullName# 验证签名validdoc.VerifySignature(fullName)ifvalid:text签名 fullName 有效\nelse:text签名 fullName 无效\ntexts.append(text)# 输出验证结果fortintexts:print(t)doc.Close()上述代码通过doc.Form获取 PDF 的 AcroForm 表单对象再包装为PdfFormWidget以访问字段集合。遍历字段时使用isinstance(field, PdfField)判断字段类型然后转换为PdfSignatureFieldWidget获取签名字段的全名。doc.VerifySignature(fullName)方法执行实际的签名验证逻辑返回布尔值表示签名是否有效。要检查已签署的文档是否在签名后被修改可以使用Signature.VerifyDocModified()方法fromspire.pdfimport*inputFile./Data/ExtractImageFromSignature.pdfdocPdfDocument()doc.LoadFromFile(inputFile)pdfformdoc.Form formWidgetPdfFormWidget(pdfform)ifformWidget.FieldsWidget.Count0:foriinrange(formWidget.FieldsWidget.Count):fieldformWidget.FieldsWidget.get_Item(i)ifisinstance(field,PdfField):signatureFieldPdfSignatureFieldWidget(field)signatureOnesignatureField.Signature modifiedsignatureOne.VerifyDocModified()ifmodified:print(文档在签名后已被修改)else:print(文档在签名后未被修改)breakdoc.Close()VerifyDocModified()方法通过比较签名时记录的文档哈希值与当前文档状态判断文档内容是否发生了变化。如果文档在签名后被修改签名虽然在技术层面仍然存在但其法律效力会受到质疑。移除数字签名当证书过期、签署流程变更或文档需要重新编辑时可能需要移除已有的数字签名。PDF 中的数字签名以 AcroForm 签名字段的形式存储通过PdfFormWidget.FieldsWidget集合可以访问并移除签名字段。移除签名字段后签名数据将一并从文档中删除。fromspire.pdfimport*inputFile./Data/SignedDocument.pdfoutputFileRemoveSignature.pdf# 加载已签名的 PDF 文档docPdfDocument()doc.LoadFromFile(inputFile)# 获取表单对象pdfformdoc.Form formWidgetPdfFormWidget(pdfform)ifformWidget.FieldsWidget.Count0:i0whileiformWidget.FieldsWidget.Count:fieldformWidget.FieldsWidget.get_Item(i)# 判断是否为签名字段ifisinstance(field,PdfField):signatureFieldPdfSignatureFieldWidget(field)# 移除该签名字段formWidget.FieldsWidget.Remove(field)breaki1# 保存文档doc.SaveToFile(outputFile)doc.Close()上述代码遍历表单字段集合找到第一个签名字段后通过FieldsWidget.Remove()方法将其移除。需要注意的是移除字段后集合的索引会立即重排因此在循环删除多个字段时建议使用while循环配合手动索引管理或从末尾倒序删除避免索引越界。如果要移除所有数字签名可以修改循环逻辑# 移除所有签名字段i0whileiformWidget.FieldsWidget.Count:fieldformWidget.FieldsWidget.get_Item(i)ifisinstance(field,PdfField):formWidget.FieldsWidget.Remove(field)# 移除后不递增索引因为后续字段前移else:i1这种写法在每次移除字段后不递增索引i因为被移除字段后面的所有字段会自动前移填补空位。只有当当前字段不是签名字段时才递增索引确保所有字段都被检查到。实用技巧提取签名中的图片某些签名外观包含签名图片如手写签名扫描件在审核或归档场景中可能需要提取这些图片。PdfFormWidget提供了ExtractSignatureAsImages属性可以直接获取所有签名中的图片对象。i0forimageinformWidget.ExtractSignatureAsImages:filenameSignatureImage-str(i).pngimage.Save(filename)i1这种方法无需手动遍历签名字段库内部会自动处理签名图片的解析和提取。启用 OCSP 在线证书验证除了时间戳还可以通过 OCSP在线证书状态协议实时验证签名证书的有效性。Security_PdfSignature类提供了ConfigureHttpOCSP()方法来配置 OCSP 验证signature.ConfigureHttpOCSP(None,None)传入None参数表示使用证书中内置的 OCSP 服务地址。启用 OCSP 后签名验证时会在线查询证书是否被吊销增强签名可信度。批量签署文档在合同批量签署场景中可以将签名逻辑封装为函数结合文件遍历实现批量处理importosdefsign_pdf(input_path,output_path,pfx_path,pfx_password):docPdfDocument()doc.LoadFromFile(input_path)signatureMakerPdfOrdinarySignatureMaker(doc,pfx_path,pfx_password)signatureMaker.MakeSignature(batchSign)doc.SaveToFile(output_path)doc.Close()# 遍历目录批量签署input_dir./contracts/output_dir./signed/forfilenameinos.listdir(input_dir):iffilename.endswith(.pdf):sign_pdf(os.path.join(input_dir,filename),os.path.join(output_dir,signed_filename),./cert/gary.pfx,e-iceblue)这种封装方式使签名逻辑可复用在处理大量合同或发票时显著减少重复代码。总结通过 Python 自动化管理 PDF 数字签名能极大地赋能合同审批与电子归档场景。结合签名验证、防篡改与时间戳机制可高效搭建文档信任链在兼顾签署效率的同时全方位保障文档的真实性与合规性。