使用IText SignDeferred签署文件时如何保留PDF-A [英] Howto keep PDF-A when signing a document using IText SignDeferred
问题描述
我确实使用IText通过延迟签名(SignDeferred)将签名应用于pdf文档. 该过程包含以下步骤:
I do apply a signature to a pdf document via delayed signing(SignDeferred) using IText. The process contains the following steps:
- 准备pdf文件以便进行后续处理
- 为pdf文档中的签名保留空间
- Prepare the pdf document for siging
- Reserve space for the signature in the pdf document
- 使用自签名证书
整个过程都有效,最后我得到了一个pdf文件,该文件中设置了签名并且有效.
The whole process works and i end with a pdf document where the signature is set and is valid.
原始pdf是 PDF-A1a ,但生成的pdf不再是有效的PDF-A1a. 我知道有一个有关IText PDF-A支持的文档(
The original pdf is a PDF-A1a but the resulting pdf is not a valid PDF-A1a anymore. I am aware that there is a documentation about IText PDF-A support (https://kb.itextpdf.com/home/it7kb/ebooks/itext-7-jump-start-tutorial-for-java/chapter-7-creating-pdf-ua-and-pdf-a-documents), but this seems to not apply since i don't change the content of the document.
我的问题: 如何使用延迟签名应用签名并将PDF-A1a保留在生成的文档中?
My question: How can i apply a signature using deferred signing and keep the PDF-A1a in the resulting document?
注意:如果我确实直接应用了签名(没有SignDeferred),则生成的pdf仍然是PDF-A1a,但是我必须使用SignDeferred 注意:我确实使用 https://www.pdfen.com/pdf-a-validator用于检查pdf-A
Note: If i do apply a signature directly (Without SignDeferred), the resulting pdf is still a PDF-A1a, but i do have to use SignDeferred Note: I do use https://www.pdfen.com/pdf-a-validator for checking pdf-A
- 用于签名的组件:
- itext.sign 7.1.5.0
- itext.kernel 7.1.5.0
- Component used for signing:
- itext.sign 7.1.5.0
- itext.kernel 7.1.5.0
- BouncyCastle.Crypto 1.8.1.0
以下是一个完整的代码示例示例,其中所有必需的内容都在一个文件中. 它只需要引用itext和BouncyCastle以及自签名证书的路径
The following is a complete code sample sample with everything required in one file. It only requires the references to itext and BouncyCastle and the path to a self signed certificate
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) { } } }
推荐答案
似乎签名的pdf不再是有效的PDF-A1a的原因是签名的估计大小. 我已经使用了大约120kb的值作为签名.
It seems the reason why the signed pdf is no longer a valid PDF-A1a is the estimated size for the signature. I have used a value of about 120kb for the signature.
//doesn't work signer.SignExternalContainer(external, 121743); //does work signer.SignExternalContainer(external, 65000);
这的概念记录在电子书"PDF文件的数字签名"中.来自iText.
The concept of this is documented in the ebook "Digital Signatures for PDF documents" from iText.
为了获得有效的pdf-A1a,最大大小似乎限制为65kb.
It seems in order to get a valid pdf-A1a the maximum size is limited to 65kb.
现在,当我添加视觉表示(签名图像)时,我将不得不测试这种方法是否有效,因为这就是我选择如此大的估计尺寸的原因.
I now will have to test whether this works when i add a visual representation (signature image) since this was the reason i did choose such a large estimated size.
我进行了更多测试,现在可以生成带有签名的有效pdf-A文档: pdf现在是有效的pdf-A,其估计大小已更改:
I did some more testing and i am now able to produce valid pdf-A document with signatures: The pdf are now valid pdf-A with the changed estimated size:
- 有效,估计大小为32'000/65'000
- A1a
- A1b
- Valid with estimate size 32'000/65'000
- A1a
- A1b
- A2a
- A2b
- A2u
- A3a
- A3b
- A3u
添加视觉表示(图像)时,pdf-A1a和pdf-A1b不再有效.
When adding a visual representation (image), pdf-A1a and pdf-A1b are no longer valid.
存在透明的软膜.从PDF 1.4开始,支持透明性.一些基于PDF的ISO标准禁止使用透明度.
A transparent soft mask is present. Beginning with PDF 1.4 transparency is supported. Some PDF-based ISO standards prohibit the use of transparency.
但这是我现在想找出的另一个问题.
But this is another problem which i now try to figure out.
这篇关于使用IText SignDeferred签署文件时如何保留PDF-A的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!