数字签名记录了谁签署了文档。受信任的时间戳记录 何时,该时间来自独立的时间戳授权机构(TSA),而不是签署者计算机上的时钟。本教程向您展示如何在 Python 中使用带时间戳的 XAdES‑T 签名对 Word 文档进行签名,然后确认时间戳已包含在文件中。

关键要点

  • 同时设置 两者 SignOptions.xml_dsig_level = XmlDsigLevel.X_AD_ES_T 和 SignOptions.timestamp_settings。单独使用其中任何一个都会生成没有时间戳的签名,且不会有错误提示。
  • Aspose.Words 在 DigitalSignatureUtil.sign 期间请求时间戳,因此该调用需要网络访问 TSA。
  • DOCX 和 DOC 文件可以添加时间戳。ODT 文件不支持。
  • DigitalSignature.is_valid 检查的是签名,而不是时间戳。请单独确认时间戳。

受信任时间戳对签名的作用

每个 Aspose.Words 创建的签名都包含一个签署时间,由 SignOptions.sign_time 设置。该值来源于签署者的机器,因此任何对文档提出异议的人也可以质疑该时间。

XAdES‑T 签名添加了独立的证据。文档签署后,Aspose.Words 将签名值的哈希发送到时间戳机构(TSA)。TSA 返回一个 RFC 3161 令牌,该令牌使用其自身的证书签名,将哈希绑定到特定时间。该令牌存储在签名内部。验证者随后可以显示该签名在该时刻已存在,这在签名证书随后过期或被吊销时尤为重要。

先决条件

在运行示例之前,请确保您已拥有:

pip install --upgrade "aspose-words>=26.9"
  • 签名证书,采用 PKCS#12 格式(.pfx 或 .p12)及其密码。
  • 时间戳授权机构 URL。 示例使用 FreeTSA (https://freetsa.org/tsr),这是一个免费公共 TSA,便于测试。对于生产文档,请使用贵组织或证书提供商推荐的 TSA,因为审阅文档的人员必须信任该 TSA 的证书。
  • TSA 凭据,仅当您的 TSA 需要身份验证时。

没有许可证,Aspose.Words 以受限的评估模式运行。一个临时许可证在您测试时会移除这些限制。

使用受信任的时间戳签署 Word 文档

以下脚本使用 XAdES‑T 签名对 DOCX 文件进行签署,并嵌入来自 TSA 的时间戳。

import datetime
import aspose.words as aw

# Replace these values with your own files and credentials.
INPUT_DOC = "contract.docx"
OUTPUT_DOC = "contract-signed.docx"
CERT_FILE = "signing-cert.pfx"
CERT_PASSWORD = "your-pfx-password"

TSA_URL = "https://freetsa.org/tsr"
TSA_USER = ""       # Fill in only if your TSA requires authentication.
TSA_PASSWORD = ""

# Load the signing certificate from a PKCS#12 file.
cert_holder = aw.digitalsignatures.CertificateHolder.create(
    file_name=CERT_FILE, password=CERT_PASSWORD)

# Request an XAdES-T signature and point it at a timestamp authority.
sign_options = aw.digitalsignatures.SignOptions()
sign_options.xml_dsig_level = aw.digitalsignatures.XmlDsigLevel.X_AD_ES_T
sign_options.timestamp_settings = aw.digitalsignatures.DigitalSignatureTimestampSettings(
    server_url=TSA_URL,
    user_name=TSA_USER,
    password=TSA_PASSWORD,
    timeout=datetime.timedelta(seconds=60),  # Default is 100 seconds.
)

# Sign the document. Aspose.Words contacts the TSA during this call.
aw.digitalsignatures.DigitalSignatureUtil.sign(
    src_file_name=INPUT_DOC,
    dst_file_name=OUTPUT_DOC,
    cert_holder=cert_holder,
    sign_options=sign_options,
)
print(f"Signed with a trusted timestamp: {OUTPUT_DOC}")

代码工作原理

  • CertificateHolder.create 从 .pfx 文件中读取私钥和证书链。密码错误会在此处失败,在任何签名开始之前。
  • XmlDsigLevel.X_AD_ES_T 告诉 Aspose.Words 构建 XAdES-T 签名,它是 XAdES-EPES 加上签名时间戳。
  • DigitalSignatureTimestampSettings 保存 TSA URL、可选的用户名和密码,以及可选的超时时间。对于接受匿名请求的 TSA,空字符串是可以的。如果 TSA 返回 HTTP 身份验证挑战,Aspose.Words 会发送您提供的凭据。
  • DigitalSignatureUtil.sign 将签名后的副本写入 OUTPUT_DOC,并保持输入文件不变。对未签名的文档进行签名:如果输入已经有签名,输出将同时包含现有签名和新签名。

检查时间戳是否已嵌入

Aspose.Words 返回的 DigitalSignature 对象不公开时间戳,且 is_valid 并不检查它。 在测试中,即使时间戳令牌被故意破坏,文档仍报告 is_valid 为 True。 要确认时间戳,请查看存储在 DOCX 包中的签名 XML:

import base64
import re
import zipfile
import aspose.words as aw

SIGNED_DOC = "contract-signed.docx"

# 1. Check the signature itself.
for sig in aw.digitalsignatures.DigitalSignatureUtil.load_signatures(SIGNED_DOC):
    print(f"Signer: {sig.subject_name} | valid: {sig.is_valid}")

# 2. Check that a timestamp token was embedded, and save it for inspection.
with zipfile.ZipFile(SIGNED_DOC) as package:
    for part in package.namelist():
        if part.startswith("_xmlsignatures/sig") and part.endswith(".xml"):
            xml = package.read(part).decode("utf-8")
            match = re.search(r"<(?:\w+:)?EncapsulatedTimeStamp[^>]*>([^<]+)<", xml)
            if match:
                with open("timestamp-token.der", "wb") as f:
                    f.write(base64.b64decode(match.group(1)))
                print(f"{part}: timestamp embedded (saved to timestamp-token.der)")
            else:
                print(f"{part}: no timestamp found")

对于已签名的 DOCX,您应该看到类似以下的输出:

Signer: CN=Your Name | valid: True
_xmlsignatures/sig1.xml: timestamp embedded (saved to timestamp-token.der)

要读取 TSA 认证的时间,请将保存的令牌传递给 OpenSSL:

openssl ts -reply -token_in -in timestamp-token.der -token_out -text

Time stamp 行显示 GMT 时间的认证时间,TSA 行标识签发该时间戳的机构。

此检查读取 DOCX 包格式。DOC 文件将其签名存储在二进制容器中,因此基于 ZIP 的脚本不适用于它。

故障排除签名和时间戳错误

症状可能原因解决方法
签名成功,但未嵌入时间戳只设置了 xml_dsig_level = X_AD_ES_T 或 timestamp_settings 中的一个在调用 sign 之前同时设置两者。使用 XML_D_SIG 或 X_AD_ES_EPES 时,时间戳设置会被忽略。
RuntimeError 提示 (401) UnauthorizedTSA 需要凭证,或凭证错误传递 TSA 提供商颁发的用户名和密码。
RuntimeError 提示连接被拒绝或代理错误TSA URL 错误,或防火墙/代理阻止请求检查 URL 并确认运行脚本的机器能够访问 TSA。
RuntimeError 提示 The operation has timed outTSA 在超时时间内未响应重试,或向 DigitalSignatureTimestampSettings 传递更长的 timeout。
错误后仍留下空的输出文件sign 在 TSA 请求失败前已创建目标文件在重试前删除目标文件,或写入临时路径并在成功调用后重命名。
RuntimeError 表示此文件格式不支持时间戳输入是 ODT 文件对 DOCX 或 DOC 文件进行时间戳,或转换为 PDF 并使用下面描述的 PDF 签名方式。
在 Linux 上出现 No usable version of libssl was found,或因缺少 ICU 包而崩溃Python 包捆绑的 .NET 运行时需要 OpenSSL 1.1 和受支持的 ICU 版本安装 OpenSSL 1.1,或安装受支持的 ICU。如果应用程序不需要特定文化的格式化,可为 ICU 错误设置 DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1。

替代方案:在 PDF 输出中为签名添加时间戳

如果您的收件人需要 PDF 而不是已签名的 Word 文件,则无需使用 DigitalSignatureUtil。通过将 PdfSaveOptions.digital_signature_details.timestamp_settings 设置为 PdfDigitalSignatureTimestampSettings 对象,在保存时对 PDF 进行签名和时间戳。此方法的可用时间远早于 26.9。该 PdfDigitalSignatureTimestampSettings reference 包含完整示例。

下一步

要添加签名行、使用签名行图像进行签名或删除现有签名,请参阅 Aspose.Words for Python 文档中的 使用数字签名。

FAQs

  1. 受信任的时间戳为数字签名添加了什么?
    时间戳授权机构(TSA)对签名创建的时间进行认证。该时间来源于独立的第三方,而非签名者的计算机时钟,并且它使验证者能够证明签名在签发证书过期或被撤销之前已经存在。

  2. 哪个 Aspose.Words 版本在 DigitalSignatureUtil 中支持时间戳?
    Aspose.Words for Python via .NET 的 26.9 版新增了 SignOptions.timestamp_settings、XmlDsigLevel.X_AD_ES_T 和 DigitalSignatureTimestampSettings 类。早期版本只能对 PDF 输出的签名进行时间戳。

  3. 我是否需要同时设置 xml_dsig_level 和 timestamp_settings?
    是的。如果只设置其中一个,Aspose.Words 仍会对文档进行签名,但不会请求或嵌入时间戳,也不会抛出错误。

  4. 哪些文件格式可以进行时间戳?
    使用 X_AD_ES_T 签名的 DOCX 和 DOC 文件会获得时间戳。对 ODT 文件进行时间戳签名会导致错误,提示该格式不支持时间戳。

  5. is_valid 能确认时间戳有效吗?
    不能。DigitalSignature.is_valid 只检查签名本身。要确认时间戳,需要检查签名 XML 中是否包含时间戳令牌,并使用诸如 OpenSSL 的工具对该令牌进行检查。

  6. 如果无法连接到 TSA 会怎样?
    DigitalSignatureUtil.sign 会抛出 RuntimeError,其中描述网络、身份验证或超时问题。目标路径可能会留下一个空文件,因而在重试前请将其删除。

获取免费许可证和支持