【发布时间】:2020-03-24 16:52:18
【问题描述】:
目标是实现一个 PDF 签名过程,其中服务器(.NET Core 服务)根据请求(电子)向客户端提供要签名的哈希。然后,客户端使用通过 PKCS#11 接口从智能卡获得的私钥对给定的散列进行签名。然后将签名发送回服务器,以便使用 iTextSharp 附加到 PDF 文件中。
目前,使用 node-webcrypto-p11 使用智能卡令牌对哈希进行签名的过程非常简单(需要大量的试验和错误才能到达这里)。使用的算法是 RSASSA-PKCS1-v1_5。我可以成功签署哈希并在之后验证它。
我最近在 External signing PDF with iTextsharp (3) 的帮助下构建了我之前的实现,我使用 getAuthenticatedAttributeBytes 来获取要签名的哈希(如 mkl 所建议的那样)。
在 Acrobat Reader 中查看签名时,我看到可怕的文档已更改/损坏,与 OP pgkdev 相同。如上所述,客户端的签名过程很简单,我不怀疑那里会出现任何问题(不过我愿意对此进行审查)。
pgkdev 提到了Priyanka's question,在那里我发现我可能在签署文档的两步过程中遇到了问题,其中哈希值不再相同。
如果您检查Grazina's question,我们可以看到这样的实现是成功的,当您一步完成该过程时。
mkl 进一步提到了一种通过 2 个步骤成功完成的方法,但我错过了有关如何实现该目标的更多解释。
注意:我(据我所知)无法在第一步中做我想做的事,因为签名是由客户在 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 以使用保存到 .txt 文件中的 messageHash。结果是一样的。 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
编辑:散列 authenticatedAttributeBytes,然后返回该散列以供客户端签名。
尝试了 3 种不同的散列方式,结果相同:
PdfHash = DigestAlgorithms.Digest(new MemoryStream(authenticatedAttributeBytes), messageDigest)
PdfHash = SHA256.Create().ComputeHash(authenticatedAttributeBytes)
PdfHash = SHA256Managed.Create().ComputeHash(authenticatedAttributeBytes)
GetPDFHash
的使用byte[] bytesToSign = GetPDFHash(Path.Combine(_configuration["documentFileSystemStore:DocumentFolder"], "SignMeMightySigner.pdf"), Convert.FromBase64String(dto.base64certificateValue));
SignPDFHash
的使用SignPDFHash(Path.Combine(_configuration["documentFileSystemStore:DocumentFolder"], "SignMeMightySigner.pdf"),Convert.FromBase64String(dto.base64signature), Convert.FromBase64String(dto.base64certificateValue));
编辑(2020 年 3 月 29 日): 我检查了我的客户端,找不到任何问题。我选择 RSASSA-PKCS1-v1_5 alg 来获取签名并在之后成功验证。在其他一些问题中,我发现在服务器和客户端之间传输字节数组可能是一个问题,但我已经检查并且值是相同的,base64 和字节数组。
决定在文本编辑器中打开 PDF,并将其与定期签名的 PDF 进行比较(相同的文本内容,只是直接通过 Adobe Reader 签名)。
让我困扰和担心的是,使用 iText 签名的 PDF 缺少直接签名的 PDF 内部的一大块“文本”。
还有什么我可以提供的可能的进一步分析吗?我已经看到 Stack Overflow 上的人们无法克服这个问题,有些人甚至完全放弃了它。我不想也不能这样做,我想追根究底。
Directly signed through Adobe Reader Deferred signing with iText
编辑 2020 年 3 月 30 日: 如上所述,我对 AuthenticatedAttributeBytes 进行哈希处理
PdfHash = SHA256Managed.Create().ComputeHash(authenticatedAttributeBytes);
AuthenticatedAttributeBytes
49 75 48 24 6 9 42 134 72 134 247 13 1 9 3 49 11 6 9 42 134 72 134 247 13 1 7 1 48 47 6 9 42 134 72 134 247 13 1 9 4 49 34 4 32 122 115 111 54 139 240 60 168 176 67 64 158 55 107 233 48 77 220 19 208 139 187 42 1 141 149 20 241 151 80 31 79
AuthenticatedAttributeBytes - 散列
33 25 105 92 244 51 72 93 179 135 158 84 249 178 103 91 236 247 253 35 232 124 169 112 108 214 63 206 206 2 88 107
(返回给客户端)AuthenticatedAttributeBytes - 散列 & base64 编码
IRlpXPQzSF2zh55U+bJnW+z3/SPofKlwbNY/zs4CWGs=
哈希签名(签名)
76 13 184 229 123 212 2 8 140 24 34 88 95 31 255 142 105 220 204 186 172 110 61 75 156 44 185 62 81 209 238 226 67 133 115 247 76 24 182 144 38 164 71 92 124 140 77 16 212 43 52 156 173 90 163 116 0 124 119 119 103 8 12 74 147 1 207 51 156 104 52 231 112 125 115 140 28 105 160 117 235 199 224 166 30 220 111 35 165 49 18 85 253 194 112 254 142 117 46 58 87 13 110 161 151 228 95 238 115 171 70 117 203 103 204 222 233 42 163 37 105 91 177 117 190 238 135 137 162 6 54 125 108 64 148 219 7 198 93 117 12 164 130 123 213 197 233 173 145 77 209 11 166 91 29 137 142 25 20 96 90 130 251 169 234 9 44 245 230 20 46 243 254 98 179 98 148 87 104 151 228 246 231 23 94 134 144 84 177 219 235 90 11 130 33 139 94 155 73 112 60 88 53 150 59 49 184 100 210 82 32 71 66 168 21 167 91 141 94 239 221 156 96 23 132 147 237 15 237 232 112 214 224 61 117 46 143 208 41 64 13 128 44 69 135 172 113 58 8 85 5 176 192 254 107 92
(从客户端收到)哈希签名(签名)-base64
TA245XvUAgiMGCJYXx//jmnczLqsbj1LnCy5PlHR7uJDhXP3TBi2kCakR1x8jE0Q1Cs0nK1ao3QAfHd3ZwgMSpMBzzOcaDTncH1zjBxpoHXrx+CmHtxvI6UxElX9wnD+jnUuOlcNbqGX5F/uc6tGdctnzN7pKqMlaVuxdb7uh4miBjZ9bECU2wfGXXUMpIJ71cXprZFN0QumWx2JjhkUYFqC+6nqCSz15hQu8/5is2KUV2iX5PbnF16GkFSx2+taC4Ihi16bSXA8WDWWOzG4ZNJSIEdCqBWnW41e792cYBeEk+0P7ehw1uA9dS6P0ClADYAsRYescToIVQWwwP5rXA==
【问题讨论】:
-
请分享由您的代码签名的 PDF 以供分析。作为该分析的结果,人们通常可以更快地找到问题的原因。
标签: c# pdf itext digital-signature