如何解决如何使用 iText IExternalSignatureContainer 提前创建 pdf 哈希
我正在使用 iText 7 将签名应用于 pdf 文档。我还使用自己的 IExternalSignatureContainer 实现,以便将证书集成到 PKCS7 CMS 中,因为签名服务仅返回 PKCS1 签名。
签名过程是异步的(用户必须进行身份验证)我想执行以下操作:
- 准备文档(PdfReader)
- 将文档的哈希值返回给用户
- 扔掉文档(PdfReader)
- 让用户进行身份验证(与 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 的不同实例上应用签名
附上一个完整的流程示例(包括签名创建,实际上需要由不同的服务完成)
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));
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,PdfSigner.CryptoStandard.CMS);
//Create the signature via own certificate
signature = CreateSignature(sh,Program.privateKeyFile,Program.privateKeyPassword);
pkcs7Signature.SetExternalDigest(signature,"RSA");
return pkcs7Signature.GetEncodedPKCS7(hash,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
}
}
解决方法
问题:有没有办法
- 在 PdfReader 的一个实例上“提前”生成 pdf 文档的哈希
- 创建签名
- 在 PdfReader 的不同实例上应用签名
iText 目前不支持此用例,特别是在每次传递中
- 生成不同的 PDF ID,
- 使用了不同的修改时间,并且
- 对于 AES 加密的 PDF,用于加密的随机数是不同的。
可以修补 iText 以在每次传递中使用相同的值,但在修补库之前,您应该考虑是否可以调整您的架构,使修补变得不必要。
例如,在您的情况下,如果您无法保留原始 PdfSigner
实例,另一种方法可能是在散列后让原始 PdfSigner
使用虚拟签名字节(例如 { {1}})。然后,在检索到签名容器后,您可以使用 new byte[0]
将其注入到不同服务中存储的文件中,只要这两个服务都可以访问共享存储(或者第一个服务至少可以将文件转发到第二个服务)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。