当前位置: 首页 > 知识库问答 >
问题:

如何在使用IText SignDeferred签署文档时保留PDF-A

符允晨
2023-03-14

我确实使用IText通过延迟签名(SignDeferred)将签名应用于pdf文档。该过程包含以下步骤:

  • 为siging准备pdf文档
    • 为pdf文档中的签名预留空间
    • 使用自签名证书

    整个过程工作,我以一个pdf文档结束,其中签名被设置并有效。

    原始pdf是pdf-A1a,但生成的pdf不再是有效的pdf-A1a。我知道有一个关于IText PDF-a支持的文档(https://kb.itextpdf.com/home/it7kb/ebooks/itext-7-jump-start-tutorial-for-java/chapter-7-creating-pdf-ua-and-pdf-a-documents),但这似乎不适用,因为我没有更改文档的内容。

    我的问题:如何使用延迟签名应用签名,并在生成的文档中保留PDF-A1a?

    注意:如果我直接应用签名(不使用SignDeferred),生成的pdf仍然是pdf-A1a,但我必须使用SignDeferred注意:我确实使用https://www.pdfen.com/pdf-a-validator用于检查pdf-A

    • 用于签名的组件:
    • 弹跳船舱。加密1.8.1.0

    以下是一个完整的代码示例示例,其中包含一个文件中所需的所有内容。它只需要引用它的文本和BouncyCastle以及自签名证书的路径

    using iText.Kernel.Pdf;
    using iText.Signatures;
    using Org.BouncyCastle.Pkcs;
    using Org.BouncyCastle.Security;
    using System;
    using System.Collections.Generic;
    using System.IO;
    
    namespace DeferredSigningTestConsole
    {
        class Program
        {
            static string SignatureAttributeName = "DeferredSignature";
            static string CertificatePath = @"C:\temp\PDFA\PdfATestCert.2pfx.pfx";
            static string CertificatePassword = "test";
    
            static void Main(string[] args)
            {
                var signedPdf = SignPdf(System.IO.File.ReadAllBytes(@"C:\temp\PDFA\PDF_A1a.pdf"));
                System.IO.File.WriteAllBytes(@"C:\temp\PDFA\signed.pdf", signedPdf);
            }
    
            public static byte[] SignPdf(byte[] pdfToSign)
            {
                byte[] hash = null;
                byte[] tmpPdf = null;
                //Step #1 >> prepare pdf for signing (Allocate space for the signature and calculate hash)
                using (MemoryStream input = new MemoryStream(pdfToSign))
                {
                    using (var reader = new PdfReader(input))
                    {
                        StampingProperties sp = new StampingProperties();
                        sp.UseAppendMode();
                        using (MemoryStream baos = new MemoryStream())
                        {
                            var signer = new PdfSigner(reader, baos, sp);
                            signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
    
                            signer.SetFieldName(SignatureAttributeName);
                            DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
    
                            signer.SignExternalContainer(external, 121743);
                            hash = external.GetDocBytesHash();
                            tmpPdf = baos.ToArray();
                        }
                    }
    
                    //Step #2 >> Create the signature based on the document hash
                    byte[] signature = GetSignatureFromHash(hash);
    
                    //Step #3 >> Apply the signature to the document
                    ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signature);
                    using (MemoryStream preparedPdfStream = new MemoryStream(tmpPdf))
                    {
                        using (var pdfReader = new PdfReader(preparedPdfStream))
                        {
                            using (PdfDocument docToSign = new PdfDocument(pdfReader))
                            {
                                using (MemoryStream outStream = new MemoryStream())
                                {
                                    PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
                                    return outStream.ToArray();
                                }
                            }
                        }
                    }
    
                }
            }
    
            public static byte[] GetSignatureFromHash(byte[] hash)
            {
                FileStream fs = new FileStream(CertificatePath, FileMode.Open);
                Pkcs12Store store = new Pkcs12Store(fs, CertificatePassword.ToCharArray());
                String alias = "";
                foreach (string al in store.Aliases)
                    if (store.IsKeyEntry(al) && store.GetKey(al).Key.IsPrivate)
                    {
                        alias = al;
                        break;
                    }
                AsymmetricKeyEntry pk = store.GetKey(alias);
                X509CertificateEntry[] chain = store.GetCertificateChain(alias);
    
                List<Org.BouncyCastle.X509.X509Certificate> c = new List<Org.BouncyCastle.X509.X509Certificate>();
                foreach (X509CertificateEntry en in chain)
                {
                    c.Add(en.Certificate);
                }
                PrivateKeySignature signature = new PrivateKeySignature(pk.Key, "SHA256");
                String hashAlgorithm = signature.GetHashAlgorithm();
                PdfPKCS7 sgn = new PdfPKCS7(null, c.ToArray(), hashAlgorithm, false);
                DateTime signingTime = DateTime.Now;
                byte[] sh = sgn.GetAuthenticatedAttributeBytes(hash, null, null, PdfSigner.CryptoStandard.CMS);
                byte[] extSignature = signature.Sign(sh);
                sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
                return sgn.GetEncodedPKCS7(hash, null, null, null, PdfSigner.CryptoStandard.CMS);
    
            }
        }
    
        internal class DigestCalcBlankSigner : IExternalSignatureContainer
        {
            private readonly PdfName _filter;
    
            private readonly PdfName _subFilter;
    
            private byte[] _docBytesHash;
    
            internal DigestCalcBlankSigner(PdfName filter, PdfName subFilter)
            {
                _filter = filter;
                _subFilter = subFilter;
            }
    
            internal virtual byte[] GetDocBytesHash()
            {
                return _docBytesHash;
            }
    
            public virtual byte[] Sign(Stream docBytes)
            {
    
                _docBytesHash = CalcDocBytesHash(docBytes);
                //If we retun the signature bytes, GetAuthenticatedAttributeBytes will throw an exception
                //Not clear how this should be done
                return new byte[0];
            }
    
            public virtual void ModifySigningDictionary(PdfDictionary signDic)
            {
                signDic.Put(PdfName.Filter, _filter);
                signDic.Put(PdfName.SubFilter, _subFilter);
            }
    
            internal static byte[] CalcDocBytesHash(Stream docBytes)
            {
                byte[] docBytesHash = null;
                docBytesHash = DigestAlgorithms.Digest(docBytes, DigestUtilities.GetDigest(DigestAlgorithms.SHA256));
                return docBytesHash;
            }
        }
    
    
        internal class ReadySignatureSigner : IExternalSignatureContainer
        {
            private byte[] cmsSignatureContents;
    
            internal ReadySignatureSigner(byte[] cmsSignatureContents)
            {
                this.cmsSignatureContents = cmsSignatureContents;
            }
    
            public virtual byte[] Sign(Stream docBytes)
            {
                return cmsSignatureContents;
            }
    
            public virtual void ModifySigningDictionary(PdfDictionary signDic)
            {
            }
        }
    }
    
    

共有1个答案

彭烨熠
2023-03-14

签名pdf不再是有效pdf-A1a的原因似乎是签名的估计大小。我使用了大约120kb的值作为签名。

//doesn't work
signer.SignExternalContainer(external, 121743);

//does work
signer.SignExternalContainer(external, 65000);

iText的电子书《PDF文档的数字签名》中记录了这一概念。

似乎为了获得有效的pdf A1a,最大大小限制为65kb。

我现在必须在添加视觉表示(签名图像)时测试这是否有效,因为这就是我选择如此大尺寸的原因。

编辑:我做了更多的测试,现在我能够生成带有签名的有效pdf-A文档:pdf现在是有效的pdf-A,估计大小已更改:

  • 估计尺寸为32'000/65'000时有效
  • A2a
  • A2b
  • A2u
  • A3a
  • A3b
  • A3u

添加视觉表示(图像)时,pdf-A1a和pdf-A1b不再有效。

有一个透明的软遮罩。从PDF 1.4开始,支持透明度。一些基于PDF的ISO标准禁止使用透明度。

但这是我现在试图解决的另一个问题。

 类似资料:
  • 我能够使用PDFBox1.8.5对PDF文档进行数字签名,多亏了PDFBOX中提供的这个出色的示例。 https://github.com/apache/pdfbox/blob/1.8/examples/src/main/java/org/apache/pdfbox/examples/signature/createSignature.java 签名此示例时,请使用本地计算机的日期/时间(第175

  • 我刚刚创建了DocuSign开发人员帐户。我必须使用签名对PDF进行签名,并用C#将其发送给收件人。我不希望收件人使用https://developers.docusign.com/esign-rest-api/code-examples/code-example-request-a-signature-via-email。我的系统中已经有PDF可用,我需要使用DocuSign API签名并将其发

  • 由于文件API不适用于移动平台,因此无论如何我们都可以使用带有UPE框架的标签来保护文档。

  • 问题内容: 在我的应用程序中,我更改了XML文件的某些部分,其开始如下: 注意之前的空白行。加载,更改和保存后,结果远非令人满意: 我发现注释和文档节点之间的空白(一个换行符)根本没有在DOM中表示。下面的独立代码可靠地重现了此问题: 有谁知道如何避免这种情况?本质上,我希望输出与输入相同。(我知道将重新生成xml声明,因为它不是DOM的一部分,但这在这里不是问题。) 问题答案: 根本原因是标准D

  • 我有一个代码是创建一个“封面”,然后将其与现有的pdf文件合并。合并后,pdf标签丢失。如何保留现有pdf的pdf标签,然后将页面标签添加到从头开始创建的pdf页面(例如“封面”)?我认为这本书的例子是关于检索和替换页面标签的。我不知道如何将一个现有的pdf和一个从头创建的pdf连接起来。我用的是itext 5.3.0。提前感谢。 根据mkl的评论进行编辑 更新 根据mkl的回答,我修改了上面的代

  • 问题内容: 我已经开始使用推荐的广告,并从移走了。我无法重新粘合在一起的一件事是使用持久性Cookie存储。我想简单地将自定义cookie处理程序/管理器附加到我的连接中以存储cookie。Android文档并不是很有帮助,因为它将Cookie主题分为两行。 我以前使用过LoopJ ,效果很好。 关于如何在Android中设置可以附加到我的自动保存和检索cookie 的持久性cookie存储的想法