目标是实现PDF签名过程,在该过程中,服务器(.NET Core服务)根据请求(Electron)向客户端提供要签名的哈希。然后,客户端使用通过PKCS#11接口从智能卡获得的私钥对给定的哈希签名。然后将签名发送回服务器,以使用iTextSharp附加到PDF文件中。
目前,使用node-webcrypto-p11使用智能卡令牌对哈希进行签名的过程非常简单(需要大量的反复试验才能到达此处。)使用的算法是RSASSA-PKCS1-v1_5。我可以成功签名哈希,然后再进行验证。
我最近在External signing PDF with iTextsharp (3)的帮助下构建了以前的实现,在其中我使用getAuthenticatedAttributeBytes获取要签名的哈希值(由[[mkl建议)。
[在Acrobat Reader中检查签名时,向我展示了与OPpgkdev]相同的已更改/损坏的可怕文档。如上所述,客户端在签名过程中很简单,我不怀疑那里会出现任何问题(不过我愿意对此进行审查)。
pgkdev
指的是Priyanka's question,在这里我发现我可能在签署文档的两步过程中遇到了问题,其中哈希值不再相同。如果您勾选Grazina's question,当您一步完成该过程时,我们可以看到这样的实现成功。mkl
进一步提到了一种成功完成此步骤的方法,但需要两个步骤,但是我缺少更多关于如何准确实现此目的的解释。注意:我(据我所知)无法一步一步完成我想做的事情,因为签名是由客户端在Electron应用程序中发起的。单击证书详细信息将显示我的完整证书详细信息。
private const string SIG_FIELD_NAME = "sigField1";
private byte[] GetPDFHash(string pdfFilePath, byte[] certificateValue)
{
var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";
//Get certificates chain from certificate value
ICollection<X509Certificate> certificatesChain = GetCertificatesChain(certificateValue);
byte[] hash = CreatePDFEmptySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);
return hash;
}
private void SignPDFHash(string pdfFilePath, byte[] hash, byte[] signedHash, byte[] certificateValue)
{
var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";
var signedPdfFilePath = $"{pdfFilePath}.signed.pdf";
//Get certificates chain from certificate value
ICollection<X509Certificate> certificatesChain = GetCertificatesChain(certificateValue);
CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);
}
private byte[] CreatePDFEmptySignature(string pdfFilePath, string preparedSigPdfFilePath, ICollection<X509Certificate> certificatesChain)
{
byte[] hash;
using (PdfReader reader = new PdfReader(pdfFilePath))
{
using (FileStream baos = System.IO.File.OpenWrite(preparedSigPdfFilePath))
{
PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
sap.SetVisibleSignature(new Rectangle(36, 720, 160, 780), 1, SIG_FIELD_NAME);
sap.Certificate = certificatesChain.First();
var externalEmptySigContainer = new MyExternalEmptySignatureContainer(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_DETACHED, preparedSigPdfFilePath, certificatesChain);
MakeSignature.SignExternalContainer(sap, externalEmptySigContainer, 8192);
hash = externalEmptySigContainer.PdfHash;
}
}
return hash;
}
private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath,
byte[] hash, byte[] signedHash, ICollection<X509Certificate> certificatesChain)
{
using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
{
using (FileStream baos = System.IO.File.OpenWrite(signedPdfFilePath))
{
IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(hash, signedHash, certificatesChain);
MakeSignature.SignDeferred(reader, SIG_FIELD_NAME, baos, externalSigContainer);
}
}
}
public class MyExternalEmptySignatureContainer : ExternalBlankSignatureContainer
{
public string PdfTempFilePath { get; set; }
public byte[] PdfHash { get; private set; }
public ICollection<X509Certificate> CertificatesList { get; set; }
public MyExternalEmptySignatureContainer(PdfName filter, PdfName subFilter, string pdfTempFilePath,
ICollection<X509Certificate> certificatesChain) : base(filter, subFilter)
{
PdfTempFilePath = pdfTempFilePath;
CertificatesList = certificatesChain;
}
override public byte[] Sign(Stream data)
{
byte[] sigContainer = base.Sign(data);
//Get the hash
IDigest messageDigest = DigestUtilities.GetDigest("SHA-256");
byte[] messageHash = DigestAlgorithms.Digest(data, messageDigest);
#region Log
var messageHashFilePath = $"{PdfTempFilePath}.messageHash-b64.txt";
System.IO.File.WriteAllText(messageHashFilePath, Convert.ToBase64String(messageHash));
#endregion Log
//Add hash prefix
byte[] sha256Prefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
byte[] digestInfo = new byte[sha256Prefix.Length + messageHash.Length];
sha256Prefix.CopyTo(digestInfo, 0);
messageHash.CopyTo(digestInfo, sha256Prefix.Length);
#region Log
var messageHashWithPrefixFilePath = $"{PdfTempFilePath}.messageHash-with-prefix-b64.txt";
System.IO.File.WriteAllText(messageHashWithPrefixFilePath, Convert.ToBase64String(digestInfo));
#endregion Log
var sgn = new PdfPKCS7(null, this.CertificatesList, "SHA256", false);
var authenticatedAttributeBytes =
sgn.getAuthenticatedAttributeBytes(messageHash, null, null, CryptoStandard.CMS);
PdfHash = authenticatedAttributeBytes;
return sigContainer;
}
}
public class MyExternalSignatureContainer : IExternalSignatureContainer
{
public byte[] Hash { get; set; }
public byte[] SignedHash { get; set; }
public ICollection<X509Certificate> CertificatesList { get; set; }
public MyExternalSignatureContainer(byte[] hash, byte[] signedHash, ICollection<X509Certificate> certificatesList)
{
Hash = hash;
SignedHash = signedHash;
CertificatesList = certificatesList;
}
public byte[] Sign(Stream data)
{
PdfPKCS7 sgn = new PdfPKCS7(null, this.CertificatesList, "SHA256", false);
sgn.SetExternalDigest(this.SignedHash, null, "RSA");
return sgn.GetEncodedPKCS7(this.Hash, null, null, null, CryptoStandard.CMS);
}
public void ModifySigningDictionary(PdfDictionary signDic) { }
}
private ICollection<X509Certificate> GetCertificatesChain(byte[] certByteArray)
{
ICollection<X509Certificate> certChain = new Collection<X509Certificate>();
X509Certificate2 cert = new X509Certificate2(certByteArray);
X509Certificate regularCert = new X509CertificateParser()
.ReadCertificate(cert.GetRawCertData());
certChain.Add(regularCert);
return certChain;
}
编辑:Signed PDF
编辑:调整了CreateFinalSignature以使用messageHash(已保存到.txt文件中)。结果是一样的。Signed PDF
private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath, byte[] signedHash, ICollection<X509Certificate> certificatesChain)
{
var messageHashFilePath = $"{preparedSigPdfFilePath}.messageHash-b64.txt";
string hashString = System.IO.File.ReadAllText(messageHashFilePath);
byte[] hash = Convert.FromBase64String(hashString);
using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
{
using (FileStream baos = System.IO.File.OpenWrite(signedPdfFilePath))
{
IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(hash, signedHash, certificatesChain);
MakeSignature.SignDeferred(reader, SIG_FIELD_NAME, baos, externalSigContainer);
}
}
}
散列相同,如下所示。在保存之前和从文件读取之后,我放置了一些断点尝试捕获这些值。
保存前的字节数组:
[0] = {byte} 133
[1] = {byte} 170
[2] = {byte} 124
[3] = {byte} 73
[4] = {byte} 225
[5] = {byte} 104
[6] = {byte} 242
[7] = {byte} 79
[8] = {byte} 44
[9] = {byte} 52
[10] = {byte} 173
[11] = {byte} 6
[12] = {byte} 7
[13] = {byte} 250
[14] = {byte} 171
[15] = {byte} 50
[16] = {byte} 226
[17] = {byte} 132
[18] = {byte} 113
[19] = {byte} 31
[20] = {byte} 125
[21] = {byte} 174
[22] = {byte} 53
[23] = {byte} 98
[24] = {byte} 68
[25] = {byte} 117
[26] = {byte} 102
[27] = {byte} 191
[28] = {byte} 109
[29] = {byte} 180
[30] = {byte} 88
[31] = {byte} 133
从CreateFinalSignature中的.txt文件读取的字节数组:
[0] = {byte} 133
[1] = {byte} 170
[2] = {byte} 124
[3] = {byte} 73
[4] = {byte} 225
[5] = {byte} 104
[6] = {byte} 242
[7] = {byte} 79
[8] = {byte} 44
[9] = {byte} 52
[10] = {byte} 173
[11] = {byte} 6
[12] = {byte} 7
[13] = {byte} 250
[14] = {byte} 171
[15] = {byte} 50
[16] = {byte} 226
[17] = {byte} 132
[18] = {byte} 113
[19] = {byte} 31
[20] = {byte} 125
[21] = {byte} 174
[22] = {byte} 53
[23] = {byte} 98
[24] = {byte} 68
[25] = {byte} 117
[26] = {byte} 102
[27] = {byte} 191
[28] = {byte} 109
[29] = {byte} 180
[30] = {byte} 88
[31] = {byte} 133
目标是实现PDF签名过程,在该过程中,服务器(.NET Core服务)根据请求(Electron)向客户端提供要签名的哈希。然后,客户端使用...
MyExternalEmptySignatureContainer.Sign
中,您可以使用PDF范围流的裸哈希正确确定经过身份验证的属性: