如何使用 itextsharp.net 将相同的数字签名放置到 PDF 中的多个位置
Posted
技术标签:
【中文标题】如何使用 itextsharp.net 将相同的数字签名放置到 PDF 中的多个位置【英文标题】:How to place the Same Digital signatures to Multiple places in PDF using itextsharp.net 【发布时间】:2017-11-28 07:28:13 【问题描述】:我已经使用 iTextSharp Dll 实现了数字签名,以使用创建空签名字段的单个签名对 PDF 文件进行签名,并使用签名哈希工作正常更新签名字段。现在,我想在 pdf 的每一页中放置相同的数字签名。这是我的客户要求。
我正在使用以下代码:
public class MyExternalSignatureContainer : IExternalSignatureContainer
private readonly byte[] signedBytes;
public MyExternalSignatureContainer(byte[] signedBytes)
this.signedBytes = signedBytes;
public byte[] Sign(Stream data)
return signedBytes;
public void ModifySigningDictionary(PdfDictionary signDic)
以下程序中使用的代码
PdfReader reader = new PdfReader(unsignedPdf);
FileStream os = File.OpenWrite(tempPdf);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Reason = "Reason1";
appearance.Contact = "";
appearance.Location = "Location1";
appearance.Acro6Layers = false;
appearance.Image = null;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 748, 144, 780), 1, null);
for (int i = 1; i < 8; i++)
var signatureField = PdfFormField.CreateSignature(stamper.Writer);
var signatureRect = new Rectangle(200, 200, 100, 100);
signatureField.Put(PdfName.T, new PdfString("ClientSignature_"+i.ToString()));
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
signatureField.Put(PdfName.V, PRef);
signatureField.Put(PdfName.F, new PdfNumber("132"));
signatureField.SetWidget(signatureRect, null);
signatureField.Put(PdfName.SUBTYPE, PdfName.WIDGET);
PdfDictionary xobject1 = new PdfDictionary();
PdfDictionary xobject2 = new PdfDictionary();
xobject1.Put(PdfName.N, appearance.GetAppearance().IndirectReference);
xobject2.Put(PdfName.AP, xobject1);
signatureField.Put(PdfName.AP, xobject1);
signatureField.SetPage();
PdfDictionary xobject3 = new PdfDictionary();
PdfDictionary xobject4 = new PdfDictionary();
xobject4.Put(PdfName.FRM, appearance.GetAppearance().IndirectReference);
xobject3.Put(PdfName.XOBJECT, xobject4);
signatureField.Put(PdfName.DR, xobject3);
stamper.AddAnnotation(signatureField, i);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
stamper.Close();
byte[] SignedHash = DoEsign(SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
os.close();
reader.close();
reader = new PdfReader(tempPdf))
os = File.OpenWrite(signedPdf)
IExternalSignatureContainer external1 = new MyExternalSignatureContainer(SignedHash);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external1);
os.close();
reader.close();
请建议我完成任务
【问题讨论】:
“客户要求” - 您是否让您的客户意识到当前 PDF 规范 ISO 32000-2 规定“文档中签名的位置可以包含影响其法律意义。因此,签名字段不得引用多个注释。和“一个给定的注释字典应该从只有一页的 Annots 数组中引用。”因此,该要求使 PDF 无效,并允许任何查看者立即或在签名验证期间拒绝它。 也就是说,this answer 显示了选项的概述(使用 ISO 32000-2 之前的 cmets); the other answer 对同一个问题给出了如何修补 iText 5.5.x 以获得所需结果的提示;该补丁是针对 Java 版本描述的,但应该很容易移植到 .Net。 我使用相同的代码做了同样的事情。请再看一遍。 “我通过使用相同的代码做了同样的事情” - 嗯,不是真的。您的方法试图走另一条路,您不要尝试从许多页面中引用单个签名字段(或像在其他地方所做的那样为单个签名字段创建多个小部件注释),您尝试创建多个签名字段共享相同的价值!作为一个有趣的想法。我不确定如果实施得当,PDF查看器会对此有何反应,据我所知,规范尚未明确禁止这样做,但也不预期...... 也就是说,你已经展示了你的代码;你跑了吗?它有效吗?如果不是,那是什么问题? (对我来说,您为签名值分配新的间接引用看起来很虚假。如果您想在所有签名字段中使用相同的签名值,所有这些字段都应该使用相同的引用...) 【参考方案1】:要为所有签名字段提供相同的单个值来包装新创建的签名容器,它们必须都引用相同的间接对象作为值。不幸的是,iText 仅在 应用程序代码有机会添加其附加字段后才为签名值创建间接对象,而这些字段又需要对该签名值对象的引用。因此,应用程序代码必须预测间接对象将拥有的对象编号。
这种对对象数量的预测或预测非常微妙,它取决于完全相同的用例,也可能由于 iTextSharp 库中的微小更改而变得不正确
为了使这更容易,应用程序代码应尽可能晚地添加这些签名字段及其签名值引用,以便在 iText 创建值间接对象之前尽可能少地创建其他新的间接对象。
事实证明,IExternalSignatureContainer
的 ModifySigningDictionary
方法是一个很好的位置。
只要在其中添加代码,就会弹出另一个问题:无法在外部设置PdfIndirectReference
实例中的预期对象编号。解决这个问题的一种方法是使用PdfLiteral
来模拟这样的引用。 (好吧,也许人们也可以为此使用反射。)
此外,事实证明,最好在构建 PdfLiteral
模仿 PdfIndirectReference
之前创建外观流以供所有其他签名字段使用,因为这简化了 iText 将用于实际值对象的对象编号的计算.
考虑到这一点,这里是概念验证。此概念证明使用IExternalSignature
实例进行实际签名。这不是必要的先决条件,也可以使用IExternalSignatureContainer
来代替,只需进行一些更改,甚至是问题中的ExternalBlankSignatureContainer
,以便稍后使用MakeSignature.SignDeferred
完成签名。
因此,给定密码参数cp
(私钥材料,例如pk.Key
用于Org.BouncyCastle.Pkcs.AsymmetricKeyEntry pk
)和证书链chain
,可以使用
PdfReader reader = new PdfReader(SRC);
FileStream os = new FileStream(DEST, FileMode.Create, FileAccess.Write);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Reason = "Reason1";
appearance.Contact = "";
appearance.Location = "Location1";
appearance.Acro6Layers = false;
appearance.Image = null;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(10, 10, 100, 100), reader.NumberOfPages, null);
IExternalSignature externalSignature = new PrivateKeySignature(cp, "SHA-256");
AllPagesSignatureContainer allPagesContainer = new AllPagesSignatureContainer(appearance, externalSignature, chain);
MakeSignature.SignExternalContainer(appearance, allPagesContainer, 8192);
使用这个外部签名容器类
public class AllPagesSignatureContainer : IExternalSignatureContainer
public AllPagesSignatureContainer(PdfSignatureAppearance appearance, IExternalSignature externalSignature, ICollection<X509Certificate> chain)
this.appearance = appearance;
this.chain = chain;
this.externalSignature = externalSignature;
public void ModifySigningDictionary(PdfDictionary signDic)
signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
PdfStamper stamper = appearance.Stamper;
PdfReader reader = stamper.Reader;
PdfDictionary xobject1 = new PdfDictionary();
PdfDictionary xobject2 = new PdfDictionary();
xobject1.Put(PdfName.N, appearance.GetAppearance().IndirectReference);
xobject2.Put(PdfName.AP, xobject1);
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + 1 + 2*(reader.NumberOfPages - 1)) + " 0 R");
for (int i = 1; i < reader.NumberOfPages; i++)
var signatureField = PdfFormField.CreateSignature(stamper.Writer);
signatureField.Put(PdfName.T, new PdfString("ClientSignature_" + i.ToString()));
signatureField.Put(PdfName.V, PRefLiteral);
signatureField.Put(PdfName.F, new PdfNumber("132"));
signatureField.SetWidget(appearance.Rect, null);
signatureField.Put(PdfName.SUBTYPE, PdfName.WIDGET);
signatureField.Put(PdfName.AP, xobject1);
signatureField.SetPage();
Console.WriteLine(signatureField);
stamper.AddAnnotation(signatureField, i);
public byte[] Sign(Stream data)
String hashAlgorithm = externalSignature.GetHashAlgorithm();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
IDigest messageDigest = DigestUtilities.GetDigest(hashAlgorithm);
byte[] hash = DigestAlgorithms.Digest(data, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
byte[] extSignature = externalSignature.Sign(sh);
sgn.SetExternalDigest(extSignature, null, externalSignature.GetEncryptionAlgorithm());
return sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
PdfSignatureAppearance appearance;
ICollection<X509Certificate> chain;
IExternalSignature externalSignature;
行中签名值的预测间接对象数
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + 1 + 2*(reader.NumberOfPages - 1)) + " 0 R");
严格依赖于“每页只有一个签名字段”的用例。对于不同的用例,估计的预测会有所不同。
我在这里再次强调这一点,因为例如this question 的 OP 在尝试“在单个页面上放置多个签名”时没有考虑到这一点。
上面对对象编号预测的另一个严格要求是PdfStamper
是如上创建的,即不是处于追加模式。如果签名作为增量更新应用,即在追加模式下,上面的行必须替换为
stamper.Writer.AddToBody(new PdfNull(), stamper.Writer.PdfIndirectReference, true);
PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");
这在this question 的上下文中产生了影响;第一行,向 PDF 添加一个间接 null 对象是必要的,以确保在具有对象流的 PDF 的情况下,对象流对象编号已经确定并且不会在下一个对象之间滑动,从而导致我们的预测出现差一错误。
注意:虽然此过程创建的内容不违反 PDF 规范的 letter(仅禁止从多个页面引用相同字段对象的情况,无论是通过相同的页面还是通过不同的小部件),它显然违反了它的意图,它的精神。因此,此过程也可能被禁止作为规范的勘误文档的一部分。
【讨论】:
以上是关于如何使用 itextsharp.net 将相同的数字签名放置到 PDF 中的多个位置的主要内容,如果未能解决你的问题,请参考以下文章
回文数或回文数是指一个像14641这样“对称”的数,即:将这个数的数字按相反的顺序重新排列后,所得到的数和原来的数一样。这里,“回文”是指像“妈妈爱我,我爱妈妈”这样的,正读反读都相同的单词或句子。