You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用PHP为PDF签名添加RFC3161时间戳?TCPDF无此功能

给PDF签名添加RFC3161时间戳的解决方案

我明白你遇到的问题:TCPDF的applyTSA功能还不支持RFC3161标准的时间戳,但你需要符合ISO 32000-1:2008(PDF 1.6)的要求,把时间戳作为未签名属性嵌入PKCS#7对象中。下面我分两种思路给你解决方法,包括手动实现和现成开源库的示例。

核心原理回顾

首先再明确下关键要求(你已经提到,但再梳理下方便后续操作):

  • 时间戳令牌必须符合RFC3161标准,嵌入PKCS#7的SignerInfo的未签名属性中
  • 对应的对象标识符(OID)是:1.2.840.113549.1.9.16.2.14(即id-aa-timeStampToken)
  • TimeStampToken里的messageImprint字段,必须是签名的SignerInfo中signature字段的哈希值(不是整个文档的哈希)

方案一:基于TCPDF手动实现

如果不想换库,你可以分四步手动完成时间戳的嵌入:

1. 用TCPDF生成签名框架,获取签名哈希

先让TCPDF生成带签名占位符的PDF,同时获取SignerInfo中signature字段的原始值(或者其哈希)。你可以通过修改TCPDF的签名相关代码,或者在签名过程中拦截这个值——比如在调用Sign()方法后,提取签名的PKCS#7结构,解析出SignerInfo里的signature字段。

2. 向RFC3161时间戳服务器请求令牌

用OpenSSL命令行或者PHP的openssl_*函数,把刚才获取的signature字段的哈希作为messageImprint,向TSA服务器请求时间戳:

命令行示例:

# 假设你已经把signature字段的二进制内容保存到sig.bin
openssl ts -query -data sig.bin -cert -sha256 | curl -H "Content-Type: application/timestamp-query" --data-binary @- https://your-tsa-server.com > ts_response.tsr

PHP代码示例:

$signatureBinary = file_get_contents('sig.bin');
$tsQuery = openssl_ts_query_create($signatureBinary, 'sha256', true);
$tsResponse = file_get_contents('https://your-tsa-server.com', false, stream_context_create([
    'http' => [
        'method' => 'POST',
        'header' => 'Content-Type: application/timestamp-query',
        'content' => $tsQuery
    ]
]));
file_put_contents('ts_response.tsr', $tsResponse);

3. 把时间戳令牌嵌入PKCS#7的未签名属性

这一步需要操作PKCS#7的ASN.1结构,把时间戳作为id-aa-timeStampToken属性添加到SignerInfo的unsignedAttributes中。你可以用PHP的ASN.1解析库(比如phpseclib/phpseclib)来完成:

use phpseclib3\File\ASN1;
use phpseclib3\File\X509;

// 读取原始的PKCS#7签名
$p7s = file_get_contents('original_signature.p7s');
$asn1 = ASN1::decodeBER($p7s);

// 读取时间戳令牌的DER编码
$tsToken = file_get_contents('ts_response.tsr');

// 构造未签名属性的ASN.1结构
$unsignedAttrs = [
    'type' => ASN1::SET,
    'children' => [
        [
            'type' => ASN1::SEQUENCE,
            'children' => [
                ['type' => ASN1::OBJECT_IDENTIFIER, 'value' => '1.2.840.113549.1.9.16.2.14'],
                [
                    'type' => ASN1::SET,
                    'children' => [
                        ['type' => ASN1::SEQUENCE, 'value' => $tsToken]
                    ]
                ]
            ]
        ]
    ]
];

// 将构造好的属性插入到SignerInfo中(需根据实际ASN.1解析结果调整节点位置)
$asn1[0]['children'][0]['children'][3]['children'][0]['children'][4] = $unsignedAttrs;

// 重新编码为DER格式
$modifiedP7s = ASN1::encodeDER($asn1);
file_put_contents('modified_signature.p7s', $modifiedP7s);

4. 把修改后的PKCS#7嵌入PDF

最后用TCPDF的setSignature()方法,传入修改后的PKCS#7文件,替换原来的签名内容,完成PDF的签名和时间戳嵌入。


方案二:使用支持RFC3161的开源库

如果不想手动处理复杂的ASN.1结构,推荐使用以下现成库,它们原生支持RFC3161时间戳:

1. mPDF(推荐,PHP库)

mPDF是一个功能完善的PHP PDF生成库,其签名模块原生支持RFC3161时间戳,配置非常简单:

require_once __DIR__ . '/vendor/autoload.php';

// 初始化mPDF
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML('<h1>已签名并添加时间戳的文档</h1>');

// 配置签名和时间戳参数
$signatureConfig = [
    'file' => '/path/to/your/certificate.pfx', // 你的PFX证书文件
    'password' => 'your-pfx-password', // 证书密码
    'location' => '北京',
    'reason' => '文档合法性验证',
    'contact' => 'admin@example.com',
    'tsa' => [
        'url' => 'https://tsa.example.com', // RFC3161时间戳服务器地址
        'hash' => 'sha256', // 哈希算法
        'username' => '', // 若TSA需要认证,填写用户名
        'password' => ''  // 认证密码
    ]
];

// 添加签名和时间戳
$mpdf->SetSignature($signatureConfig);

// 输出PDF
$mpdf->Output('signed_with_ts.pdf', 'I');

2. PDFLib(PHP绑定)

PDFLib是一个专业的PDF处理库,其pdc_sign_pdf函数支持直接添加RFC3161时间戳作为签名属性,适合复杂的PDF处理场景。

3. iText 7(Java/ .NET/ Python)

如果你不限于PHP,iText 7是行业标准的PDF处理库,完全支持RFC3161时间戳的嵌入,文档和示例非常丰富。


注意事项

  • 确保你的时间戳服务器支持RFC3161协议,比如全球通用的https://freetsa.org/tsr(免费测试用)
  • 哈希算法要和签名使用的算法一致(比如签名用SHA256,时间戳也要用SHA256)
  • 手动处理PKCS#7时,一定要严格遵循ASN.1编码规则,避免结构错误导致PDF签名验证失败

内容的提问来源于stack exchange,提问作者h2odev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:35:00