
本文详解使用 iText 7(.NET 版)在 PDF 同一页上添加多个独立数字签名的正确方法,解决因重复使用已关闭 PdfSigner 导致的“Document was closed”异常,并强调认证签名层级限制与追加模式的关键实践。
本文详解使用 itext 7(.net 版)在 pdf 同一页上添加多个独立数字签名的正确方法,解决因重复使用已关闭 `pdfsigner` 导致的“document was closed”异常,并强调认证签名层级限制与追加模式的关键实践。
在 PDF 数字签名实践中,一个常见误区是试图复用同一个 PdfSigner 实例进行多次签名操作。正如问题中所示,调用 signer.SignDetached() 后,底层 PdfDocument 会被自动关闭,后续再调用 signDetail() 将抛出 PdfException: 'Document was closed' —— 这并非 Bug,而是 iText 7 的明确设计:每个 PdfSigner 实例仅支持一次签名操作。
要实现在同一 PDF 页面(如第 1 页)上叠加多个签名(例如申请人、部门主管、法务三方签署),必须采用「分步追加签名」策略:每次签名后保存中间文件,再以该文件为输入创建新的 PdfSigner 实例。关键在于全程启用 UseAppendMode() 并确保 PreserveEncryption(),以维持原有加密与签名结构完整性。
以下是修正后的完整实现逻辑(含关键注释):
public void signMultiple()
{
string KEYSTORE = @"D:\resources\cert.pfx";
char[] PASSWORD = "password".ToCharArray();
// 加载证书链与私钥(此处省略细节,保持原逻辑)
Pkcs12Store pk12 = new Pkcs12Store(new FileStream(KEYSTORE, FileMode.Open), PASSWORD);
string alias = pk12.Aliases.Cast<string>()
.FirstOrDefault(a => pk12.IsKeyEntry(a));
ICipherParameters pk = pk12.GetKey(alias).Key;
X509CertificateEntry[] ce = pk12.GetCertificateChain(alias);
IX509Certificate[] chain = ce.Select(e => new X509CertificateBC(e.Certificate)).ToArray();
string SRC = @"D:\resources\PR.pdf";
string INT = @"D:\resources\PR_intermediate.pdf"; // 中间文件(首次签名后保存)
string DEST = @"D:\resources\PR_signed.pdf"; // 最终输出
Rectangle requesterPosition = new Rectangle(10, 140, 130, 180);
Rectangle headOfPosition = new Rectangle(160, 140, 300, 180);
// 第一次签名:从原始 PDF 开始
using (PdfReader reader = new PdfReader(SRC))
using (FileStream fs = new FileStream(INT, FileMode.Create))
{
PdfSigner signer = new PdfSigner(
reader,
fs,
new StampingProperties().PreserveEncryption().UseAppendMode()
);
signDetail(signer, chain, pk, "req", requesterPosition);
}
// 第二次签名:以中间文件为输入
using (PdfReader reader = new PdfReader(INT))
using (FileStream fs = new FileStream(DEST, FileMode.Create))
{
PdfSigner signer = new PdfSigner(
reader,
fs,
new StampingProperties().PreserveEncryption().UseAppendMode()
);
// ⚠️ 重要:第二次签名不可再设为 CERTIFIED(仅首个签名可认证)
signDetailForApproval(signer, chain, pk, "scm", headOfPosition);
}
}
private void signDetail(PdfSigner signer, IX509Certificate[] chain,
ICipherParameters pk, string sigName, Rectangle rect)
{
// 首个签名可设为认证签名(锁定表单/注释修改)
signer.SetCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS);
signer.SetFieldName(sigName); // 显式指定字段名,避免自动生成冲突
PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
appearance.SetReason("Applicant signature")
.SetLocation("VN")
.SetPageRect(rect)
.SetPageNumber(1);
IExternalSignature pks = new PrivateKeySignature(
new PrivateKeyBC(pk),
DigestAlgorithms.SHA256
);
signer.SignDetached(pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
}
private void signDetailForApproval(PdfSigner signer, IX509Certificate[] chain,
ICipherParameters pk, string sigName, Rectangle rect)
{
// ❗ 第二个及后续签名必须降级为普通签名(非认证)
// 否则 PDF 验证器可能拒绝或标记为无效
signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
signer.SetFieldName(sigName);
PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
appearance.SetReason("Department head approval")
.SetLocation("VN")
.SetPageRect(rect)
.SetPageNumber(1);
IExternalSignature pks = new PrivateKeySignature(
new PrivateKeyBC(pk),
DigestAlgorithms.SHA256
);
signer.SignDetached(pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
}</string>
关键注意事项总结:
- ✅ 必须分步签名:每次
SignDetached()后重建PdfSigner,不可复用; - ✅ 强制启用 Append Mode:
UseAppendMode()是多签名的基石,确保新签名以增量方式写入,不破坏原有内容; - ⚠️ 认证签名唯一性:PDF 规范规定仅第一个签名可设为
CERTIFIED_*级别,后续签名须设为NOT_CERTIFIED,否则 Adobe Acrobat 等验证工具将报错; - ? 资源安全释放:务必使用
using语句确保PdfReader和FileStream及时释放,避免文件锁或内存泄漏; - ? 签名位置不重叠:确保
Rectangle参数在同一页内坐标互斥,避免视觉覆盖或签名域冲突。
通过此方案,您可在单页 PDF 上稳健部署多角色电子签名流程,同时完全兼容 ISO 32000-2(PDF 2.0)标准与主流验证工具。










