C# 如何使用iText IExternalSignatureContainer提前创建pdf哈希

C# 如何使用iText IExternalSignatureContainer提前创建pdf哈希,c#,itext,pdf-generation,itext7,C#,Itext,Pdf Generation,Itext7,我正在使用iText7对pdf文档应用签名。我还使用自己的IExternalSignatureContainer实现将证书集成到PKCS7 CMS中,因为签名服务只返回PKCS1签名 签名过程是异步的(用户必须进行身份验证),我希望执行以下操作: 准备文档(PDF阅读器) 将文档的哈希值返回给用户 扔掉文档(PDF阅读器) 让用户进行身份验证(与iText签名过程没有直接关系)并创建签名(PKCS1) 如果用户已通过身份验证,请重新准备文档并应用签名 原因是我没有太多的时间将准备好的文档保存

我正在使用iText7对pdf文档应用签名。我还使用自己的IExternalSignatureContainer实现将证书集成到PKCS7 CMS中,因为签名服务只返回PKCS1签名

签名过程是异步的(用户必须进行身份验证),我希望执行以下操作:

  • 准备文档(PDF阅读器)
  • 将文档的哈希值返回给用户
  • 扔掉文档(PDF阅读器)
  • 让用户进行身份验证(与iText签名过程没有直接关系)并创建签名(PKCS1)
  • 如果用户已通过身份验证,请重新准备文档并应用签名
原因是我没有太多的时间将准备好的文档保存在内存中,也没有太多的时间用于批签名

我的问题是,创建的哈希值总是不同的。(即使我通过pdfSigner.SetSignDate将日期/时间设置为相同的值)或每个PdfReader/pdfSigner实例

            //Create the hash of of the pdf document 
            //Part of my IExternalSignatureContainer Sign method
            //Called from iText pdfSigner.SignExternalContainer
            //The produced hash is always different
            byte[] hash = DigestAlgorithms.Digest(pdfStream, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
问题:有没有办法

  • 在PdfReader的一个实例上“提前”生成pdf文档的哈希
  • 创建签名
  • 在PdfReader的不同实例上应用签名
附件是流程的完整示例(包括签名创建,实际上需要由其他服务完成)

使用系统;
使用System.IO;
使用System.Security.Cryptography;
使用System.Security.Cryptography.X509证书;
使用系统文本;
使用iText.Kernel.Pdf;
使用iText.Signatures;
使用Org.BouncyCastle.X509;
使用X509Certificate=Org.BouncyCastle.X509.X509Certificate;
命名空间SignExternalTestManuel
{
班级计划
{
常量字符串文件路径=@“c:\temp\pdfsign\”;
公共静态字符串pdfToSign=Path.Combine(filePath,@“test.pdf”);
公共静态字符串destinationFile=Path.Combine(filePath,“test_signed.pdf”);
公共静态字符串LocalUserCertificatePublicKey=Path.Combine(文件路径,“BITSignTestManuel5Base64.cer”);
公共静态字符串LocalCaCertificatePublicKey=Path.Combine(文件路径,“BITRoot5Base64.cer”);
公共静态字符串privateKeyFile=Path.Combine(filePath,“BITSignTestManuel5.pfx”);
公共静态字符串privateKeyPassword=“test”;
公共静态void Main(字符串[]args)
{
PDF阅读器=新的PDF阅读器(pdfToSign);
使用(FileStream os=newfilestream(destinationFile,FileMode.OpenOrCreate))
{
StampingProperties StampingProperties=新的StampingProperties();
stampingProperties.UseAmpendMode();
PdfSigner PdfSigner=新的PdfSigner(读卡器、操作系统、冲压属性);
pdfSigner.SetCertificationLevel(pdfSigner.NOT_CERTIFIED);
IExternalSignatureContainer external=新的GsSignatureContainer(
PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_分离);
pdfSigner.SetSignDate(新的日期时间(2021,2,22,10,0,0));
pdfSigner.SetFieldName(“MySignatureField”);
pdfSigner.SignExternalContainer(外部,32000);
}
}
}
公共类GsSignatureContainer:IExternalSignatureContainer
{
私人PdfDictionary sigDic;
公共GsSignatureContainer(PdfName筛选器、PdfName子筛选器)
{
sigDic=新的PdfDictionary();
sigDic.Put(PdfName.Filter,Filter);
sigDic.Put(PdfName.SubFilter,SubFilter);
}
/// 
///基于https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7#HowtouseaDigitalSigningService(DSS)如全局信号,带有ITEXT7示例代码
/// 
/// 
/// 
公共字节[]符号(流pdfStream)
{
//创建证书链G因为签名只是PKCS1,所以必须将证书添加到签名中
X509Certificate[]链=null;
字符串cert=System.IO.File.ReadAllText(Program.LocalUserCertificatePublicKey);
字符串ca=System.IO.File.ReadAllText(Program.LocalCaCertificatePublicKey);
链=创建链(证书,ca);
X509CrlParser p=新的X509CrlParser();
字符串hashAlgorithm=DigestAlgorithms.SHA256;
PdfPKCS7 pkcs7Signature=新的PdfPKCS7(null、chain、hashAlgorithm、false);
//创建pdf文档的哈希值
//我的IExternalSignatureContainer签名方法的一部分
//从iText pdfSigner.SignExternalContainer调用
//生成的哈希总是不同的
byte[]hash=DigestAlgorithms.Digest(pdfStream,DigestAlgorithms.GetMessageDigest(hashAlgorithm));
字节[]签名=null;
//基于文档哈希创建哈希,该哈希适合使用SHA256和X509Certificate进行pdf签名
字节[]sh=pkcs7Signature.GetAuthenticatedAttributeBytes(散列、null、null、PdfSigner.CryptoStandard.CMS);
//通过自己的证书创建签名
签名=CreateSignature(sh,Program.privateKeyFile,Program.privateKeyPassword);
pkcs7Signature.SetExternalDigest(签名,null,“RSA”);
返回pkcs7Signature.GetEncodedPKCS7(散列,null,null,null,PdfSigner.CryptoStandard.CMS);
}
公共无效修改签名字典(PdfDictionary signDic)
{
sigDic.PutAll(sigDic);
}
私有静态X509Certificate[]CreateChain(字符串证书,字符串ca)
{
//注意:根证书可以省略,它仍然可以工作
X509Certificate[]chainy=新的X509Certificate[2];
X509CertificateParser解析器=新的X50
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using iText.Kernel.Pdf;
using iText.Signatures;
using Org.BouncyCastle.X509;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;

namespace SignExternalTestManuel
{
    class Program
    {
        const string filePath = @"c:\temp\pdfsign\";
        public static string pdfToSign = Path.Combine(filePath, @"test.pdf");
        public static string destinationFile = Path.Combine(filePath, "test_signed.pdf");
        public static string LocalUserCertificatePublicKey = Path.Combine(filePath, "BITSignTestManuel5Base64.cer");
        public static string LocalCaCertificatePublicKey = Path.Combine(filePath, "BITRoot5Base64.cer");
        public static string privateKeyFile = Path.Combine(filePath, "BITSignTestManuel5.pfx");
        public static string privateKeyPassword = "test";

        public static void Main(String[] args)
        {
            PdfReader reader = new PdfReader(pdfToSign);
            using (FileStream os = new FileStream(destinationFile, FileMode.OpenOrCreate))
            {
                
                StampingProperties stampingProperties = new StampingProperties();
                stampingProperties.UseAppendMode();
                PdfSigner pdfSigner = new PdfSigner(reader, os, stampingProperties);
                pdfSigner.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);

                IExternalSignatureContainer external = new GsSignatureContainer(
                    PdfName.Adobe_PPKLite,
                    PdfName.Adbe_pkcs7_detached);

                pdfSigner.SetSignDate(new DateTime(2021, 2, 22, 10, 0, 0));

                pdfSigner.SetFieldName("MySignatureField");
                pdfSigner.SignExternalContainer(external, 32000);
            }
        }
    }


    public class GsSignatureContainer : IExternalSignatureContainer
    {
        private PdfDictionary sigDic;


        public GsSignatureContainer(PdfName filter, PdfName subFilter)
        {
            sigDic = new PdfDictionary();
            sigDic.Put(PdfName.Filter, filter);
            sigDic.Put(PdfName.SubFilter, subFilter);
        }

        /// <summary>
        /// Implementation based on https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7#HowtouseaDigitalSigningService(DSS)suchasGlobalSign,withiText7-Examplecode
        /// </summary>
        /// <param name="pdfStream"></param>
        /// <returns></returns>
        public byte[] Sign(Stream pdfStream)
        {
            //Create the certificate chaing since the signature is just a PKCS1, the certificates must be added to the signature
            X509Certificate[] chain = null;


            string cert = System.IO.File.ReadAllText(Program.LocalUserCertificatePublicKey);
            string ca = System.IO.File.ReadAllText(Program.LocalCaCertificatePublicKey);
            chain = CreateChain(cert, ca);

            X509CrlParser p = new X509CrlParser();

            String hashAlgorithm = DigestAlgorithms.SHA256;
            PdfPKCS7 pkcs7Signature = new PdfPKCS7(null, chain, hashAlgorithm, false);

            //Create the hash of of the pdf document 
            //Part of my IExternalSignatureContainer Sign method
            //Called from iText pdfSigner.SignExternalContainer
            //The produced hash is always different
            byte[] hash = DigestAlgorithms.Digest(pdfStream, DigestAlgorithms.GetMessageDigest(hashAlgorithm));

            byte[] signature = null;

            //Create the hash based on the document hash which is suitable for pdf siging with SHA256 and a X509Certificate
            byte[] sh = pkcs7Signature.GetAuthenticatedAttributeBytes(hash, null, null, PdfSigner.CryptoStandard.CMS);
            //Create the signature via own certificate
            signature = CreateSignature(sh, Program.privateKeyFile, Program.privateKeyPassword);
            pkcs7Signature.SetExternalDigest(signature, null, "RSA");
            return pkcs7Signature.GetEncodedPKCS7(hash, null, null, null, PdfSigner.CryptoStandard.CMS);
        }

        public void ModifySigningDictionary(PdfDictionary signDic)
        {
            signDic.PutAll(sigDic);
        }

        private static X509Certificate[] CreateChain(String cert, String ca)
        {
            //Note: The root certificate could be omitted and it would still work
            X509Certificate[] chainy = new X509Certificate[2];
            X509CertificateParser parser = new X509CertificateParser();
            chainy[0] = new X509Certificate(parser.ReadCertificate(Encoding.UTF8.GetBytes(cert))
                .CertificateStructure);
            chainy[1] = new X509Certificate(parser.ReadCertificate(Encoding.UTF8.GetBytes(ca))
                .CertificateStructure);
            return chainy;
        }

        #region "Create signature, will be done by an actual service"
        private byte[] CreateSignature(byte[] hash, string privateKeyFile, string privateKeyPassword)
        {
            //Sign data directly with a X509Certificate
            X509Certificate2 rootCertificateWithPrivateKey = new X509Certificate2();
            byte[] rawData = System.IO.File.ReadAllBytes(privateKeyFile);
            rootCertificateWithPrivateKey.Import(rawData, privateKeyPassword, X509KeyStorageFlags.Exportable);

            using (var key = rootCertificateWithPrivateKey.GetRSAPrivateKey())
            {
                return key.SignData(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            }
        }
        #endregion


    }
}