如何使用 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 创建值间接对象之前尽可能少地创建其他新的间接对象。

事实证明,IExternalSignatureContainerModifySigningDictionary 方法是一个很好的位置。

只要在其中添加代码,就会弹出另一个问题:无法在外部设置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 中的多个位置的主要内容,如果未能解决你的问题,请参考以下文章

为所有实例设置组件属性/将相同的数据传递给所有组件实例

excel同列中不能输入相同的数设置

arcgis如何把value值减去同样的数

2个json数据,如何把相同的数组合并成一个数组?

如何避免在 rand() 函数生成的矩阵中使用相同的成员?

回文数或回文数是指一个像14641这样“对称”的数,即:将这个数的数字按相反的顺序重新排列后,所得到的数和原来的数一样。这里,“回文”是指像“妈妈爱我,我爱妈妈”这样的,正读反读都相同的单词或句子。