如何在使用 IText SignDeferred 签署文档时保留 PDF-A
Howto keep PDF-A when signing a document using IText SignDeferred
我确实使用 IText 通过延迟签名 (SignDeferred) 将签名应用于 pdf 文档。
该过程包含以下步骤:
- 准备 pdf 文档以供签名
- 为pdf文档中的签名预留space
- 创建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
代码示例
- 用于签名的组件:
- itext.sign 7.1.5.0
- itext.kernel 7.1.5.0
- 用于创建散列的组件
- BouncyCastle.Crypto 1.8.1.0
以下是一个完整的代码示例,一个文件中包含所有必需的内容。
它只需要对 itext 和 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)
{
}
}
}
签名的 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
- A1a
- A1b
- 估计尺寸 32'000 有效
- A2a
- A2b
- A2u
- A3a
- A3b
- A3u
添加视觉表示(图像)时,pdf-A1a 和 pdf-A1b 不再有效。
A transparent soft mask is present. Beginning with PDF 1.4 transparency is supported. Some PDF-based ISO standards prohibit the use of transparency.
但这是我现在试图弄清楚的另一个问题。
我确实使用 IText 通过延迟签名 (SignDeferred) 将签名应用于 pdf 文档。 该过程包含以下步骤:
- 准备 pdf 文档以供签名
- 为pdf文档中的签名预留space
- 创建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
代码示例
- 用于签名的组件:
- itext.sign 7.1.5.0
- itext.kernel 7.1.5.0
- 用于创建散列的组件
- BouncyCastle.Crypto 1.8.1.0
以下是一个完整的代码示例,一个文件中包含所有必需的内容。 它只需要对 itext 和 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)
{
}
}
}
签名的 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
- A1a
- A1b
- 估计尺寸 32'000 有效
- A2a
- A2b
- A2u
- A3a
- A3b
- A3u
添加视觉表示(图像)时,pdf-A1a 和 pdf-A1b 不再有效。
A transparent soft mask is present. Beginning with PDF 1.4 transparency is supported. Some PDF-based ISO standards prohibit the use of transparency.
但这是我现在试图弄清楚的另一个问题。