我正在尝试通过外部网络服务签署 pdf。Web 服务接收 SHA-256 十六进制编码的哈希,并在签名时返回一个十六进制摘要签名。
目前,我一直在尝试使用 iText 7 (c#) 库尝试使用签名值对文档进行签名。我确实设法使用本地自签名证书使用 itext 签署了一个 pdf 文档,但还没有使用外部容器,adobe acrobat 总是报告 BER 解码问题,这让我相信解析时出现问题我这边或签名值不正确。
软件示例的签名步骤如下:
首先添加一个空白 dignature ,签署此文档,然后生成一个哈希 (SHA-256)。
public static void CalculateHash(Stream fileStream, out byte[] docBytesHash, out byte[] preSignedBytes) { PdfName filter = PdfName.Adobe_PPKLite; PdfName subFilter = PdfName.Adbe_pkcs7_detached; int estimatedSize = 8192; PdfReader reader = new PdfReader(fileStream); MemoryStream baos = new MemoryStream(); PdfSigner signer = new PdfSigner(reader, baos, new StampingProperties()); signer.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED); PdfSignatureAppearance appearance = signer.GetSignatureAppearance(); appearance.SetLayer2Text("Signature field which signing is deferred.").SetPageRect(new Rectangle(36, 600, 200, 100)).SetPageNumber(1); signer.SetFieldName("DeferredSignature1"); DigestCalcBlankSigner external = new DigestCalcBlankSigner(filter, subFilter); //IExternalSignatureContainer external = new ExternalBlankSignatureContainer(filter, subFilter); signer.SignExternalContainer(external, estimatedSize); docBytesHash = external.GetDocBytesHash(); preSignedBytes = baos.ToArray(); }
哈希转换为字节数组并发送到 web 服务
public static string ByteArrayToString(byte[] array) { StringBuilder stringBuilder = new StringBuilder(); foreach (byte b in array) stringBuilder.AppendFormat("{0:x2}", b); return stringBuilder.ToString(); }
Web 服务使用签名数据回复字符串数据的示例
"473e8e376ca067f3c806902f718be21bf8a788ddbd31786b14fc47678596d6993a4f1ecb80e091f93af4820a75d97aee4b1a15c4a7914b4f881ca86e5d06b429b176d5b663c986c9ce2824333c98e0b5def0af53178b9ce38aa4efaa0adce2eee409487fb7fecf58e4c5bfcc3a0d083e35a83f9c722c73b78784e9990b6f00b89ae4934714c92b34699ce00ad5a662d0058bd613021449e9d09ab2d25376230de75591ab6ce4b5c5d24216794e8c871a690b4e19011621d41c66f4b0048abc9f2d4449072ee9e70c30dcf9b8b5a1ea8ee3a285163c2c5b293a3798a4a13ca59e83c66d9148d519b55e13643a3a7e0794732b92f50c1424f7be5774f67e910076"
然后使用以下函数将字符串转换为字节数组
public static byte[] StringToByteArray(String hex) { int NumberChars = hex.Length; byte[] bytes = new byte[NumberChars / 2]; for (int i = 0; i < NumberChars; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); return bytes; }
现在使用字节数组签名构造签名容器,并使用容器和预签名文档字节对文档进行签名
using (System.IO.FileStream outStream = new System.IO.FileStream(System.IO.Path.GetTempPath() + "tempout.pdf", System.IO.FileMode.Create)) { PdfName filter = PdfName.Adobe_PPKLite; PdfName subFilter = PdfName.Adbe_pkcs7_detached; MemoryStream baos = new MemoryStream(); fileStream.CopyTo(baos); //temp byte[] preSignedBytes = baos.ToArray(); ReadySignatureSigner extSigContainer = new ReadySignatureSigner(StringHelper.StringToByteArray(signature.data.attributes.signature)); //now use external provider signature PdfDocument docToSign = new PdfDocument(new PdfReader(new MemoryStream(preSignedBytes))); PdfSigner.SignDeferred(docToSign, "DeferredSignature1", outStream, extSigContainer); docToSign.Close(); }
我还在下面的链接中添加了原始 pdf、未签名 pdf 和签名 pdf 以供参考。我希望有人可以看看内容,也许可以告诉我这里出了什么问题。我已经在网上花了很多时间寻找解决方案,但到目前为止还没有找到。
问候,乔斯·艾勒斯
感谢 mkl 的回答,我重写了构建 PKCS 容器的代码,如下所示,首先创建哈希和预签名字节并将其发送到签名服务:
Org.BouncyCastle.X509.X509Certificate[] signChain = new Org.BouncyCastle.X509.X509Certificate[1];
signChain[0] = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(signingcert);
PdfName filter = PdfName.Adobe_PPKLite;
PdfName subFilter = PdfName.Adbe_pkcs7_detached;
int estimatedSize = 8192;
PdfReader reader = new PdfReader(memoryStream);
MemoryStream baos = new MemoryStream();
PdfSigner signer = new PdfSigner(reader, baos, new StampingProperties());
signer.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);
PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
appearance.SetLayer2Text("Signature field which signing is deferred.").SetPageRect(new Rectangle(36, 600,
200, 100)).SetPageNumber(1);
signer.SetFieldName("DeferredSignature1");
DigestCalcBlankSigner external = new DigestCalcBlankSigner(filter, subFilter);
signer.SignExternalContainer(external, estimatedSize);
var signatureContainer = new PdfPKCS7(null, signChain, HASH_ALGORITHM, true);
preSignedBytes = baos.ToArray();
sh = signatureContainer.GetAuthenticatedAttributeBytes(preSignedBytes, null, null, CryptoStandard.CMS);
然后我使用返回的签名并创建新的 PKCS7 容器并执行延迟签名,如下所示(提到的哈希是上一节中的字节数组 sh):
var signatureContainer = new PdfPKCS7(null, signChain, HASH_ALGORITHM, true);
signatureContainer.SetExternalDigest(StringHelper.StringToByteArray(signature.data.attributes.signature), null, "RSA");
byte[] encodedSignature = signatureContainer.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
ReadySignatureSigner extSigContainer = new ReadySignatureSigner(encodedSignature); //now use external provider signature
memoryStream.Position = 0;
PdfDocument docToSign = new PdfDocument(new PdfReader(memoryStream));
MemoryStream memoryOutStream = new MemoryStream();
PdfSigner.SignDeferred(docToSign, "DeferredSignature1", memoryOutStream, extSigContainer);
目前证书在pdf中可见,但现在我仍然有文件已被更改的错误。我现在有点卡住了,有什么建议吗?我也很好奇这里是否需要使用私钥(如果有必要,我还没有从提供商那里得到回复)。
有没有人有任何见解,使用的网络服务来自 Digiidentity