阅读以下参考资料:
哈希代码:
BouncyCastle.X509Certificate[] chain = Utils.GetSignerCertChain();
reader = Utils.GetReader();
MemoryStream stream = new MemoryStream();
using (var stamper = PdfStamper.CreateSignature(reader, stream, '\0'))
{
PdfSignatureAppearance sap = stamper.SignatureAppearance;
sap.SetVisibleSignature(
new Rectangle(36, 740, 144, 770),
reader.NumberOfPages,
"SignatureField"
);
sap.Certificate = chain[0];
sap.SignDate = DateTime.Now;
sap.Reason = "testing web context signatures";
PdfSignature pdfSignature = new PdfSignature(
PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED
);
pdfSignature.Date = new PdfDate(sap.SignDate);
pdfSignature.Reason = sap.Reason;
sap.CryptoDictionary = pdfSignature;
Dictionary<PdfName, int> exclusionSizes = new Dictionary<PdfName, int>();
exclusionSizes.Add(PdfName.CONTENTS, SIG_BUFFER * 2 + 2);
sap.PreClose(exclusionSizes);
Stream sapStream = sap.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(
sapStream,
DigestAlgorithms.SHA256
);
// is this needed?
PdfPKCS7 sgn = new PdfPKCS7(
null, chain, DigestAlgorithms.SHA256, true
);
byte[] preSigned = sgn.getAuthenticatedAttributeBytes(
hash, sap.SignDate, null, null, CryptoStandard.CMS
);
var hashedValue = Convert.ToBase64String(preSigned);
}
只是一个简单的测试 - 在初始页面请求上创建虚拟Pdf文档,计算哈希值,并将其放入隐藏的输入字段Base64编码。 (上面hashedValue
)
然后在客户端使用CAPICOM来发布表单并获取用户的签名响应:
PdfSignatureAppearance sap = (PdfSignatureAppearance)TempData[TEMPDATA_SAP];
PdfPKCS7 sgn = (PdfPKCS7)TempData[TEMPDATA_PKCS7];
stream = (MemoryStream)TempData[TEMPDATA_STREAM];
byte[] hash = (byte[])TempData[TEMPDATA_HASH];
byte[] originalText = (Encoding.Unicode.GetBytes(hashValue));
// Oid algorithm verified on client side
ContentInfo content = new ContentInfo(new Oid("RSA"), originalText);
SignedCms cms = new SignedCms(content, true);
cms.Decode(Convert.FromBase64String(signedValue));
// CheckSignature does not throw exception
cms.CheckSignature(true);
var encodedSignature = cms.Encode();
/* tried this too, but no effect on result
sgn.SetExternalDigest(
Convert.FromBase64String(signedValue),
null,
"RSA"
);
byte[] encodedSignature = sgn.GetEncodedPKCS7(
hash, sap.SignDate, null, null, null, CryptoStandard.CMS
);
*/
byte[] paddedSignature = new byte[SIG_BUFFER];
Array.Copy(encodedSignature, 0, paddedSignature, 0, encodedSignature.Length);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(
PdfName.CONTENTS,
new PdfString(paddedSignature).SetHexWriting(true)
);
sap.Close(pdfDictionary);
所以现在我不确定我是否搞乱了散列部分,签名部分或两者兼而有之。在上面的签名代码片段和客户端代码(未显示)中,我正在调用我认为的签名验证码,但这也可能是错误的,因为这对我来说是第一次。在打开PDF时,获取臭名昭着的“ 文档已被更改或损坏,因为它已签名 ”无效的签名消息。
客户端代码(不是我创作的)can be found here。 Source有一个变量命名错误,已更正。作为参考,CAPICOM文档说明已签署的回复is in PKCS#7 format。
编辑2015-03-12 :
经过@mkl和更多研究的一些不错的指示,在这种情况下,似乎CAPICOM 实际上无法使用。虽然根据here和here没有明确记录,(还有什么是新的?),CAPICOM期望将utf16字符串(.NET中的Encoding.Unicode
)作为输入来创建数字签名。从那里它填充或截断(取决于前一句中的哪个源正确)如果长度是奇数,它接收的任何数据。即如果PdfSignatureAppearance.GetRangeStream()返回的Stream
的长度为奇数,则签名创建将始终失败。也许我应该创建一个我很幸运选项:如果远程流长度是偶数则签名,如果奇数则抛出InvalidOperationException
。 (悲伤的幽默尝试)
供参考,这是测试项目。
编辑2015-03-25 :
关闭此循环,here's a link to a VS 2013 ASP.NET MVC project。可能不是最佳方式,但确实为问题提供完全可行的解决方案。由于CAPICOM奇怪且不灵活的签名实现,如上所述,知道可能的解决方案可能需要第二次传递,并且如果PdfSignatureAppearance.GetRangeStream()的返回值(再次,Stream.Length
)注入额外字节的方法是一个奇数。我打算通过填充PDF内容来尝试漫长而艰难的方式,但幸运的是,同事发现填充PdfSignatureAppearance.Reason
要容易得多。需要第二次通过才能使用iText [夏普],这并不是史无前例的 - 例如adding page x of y for a document page header/footer
答案 0 :(得分:1)
在计算范围流摘要之后和将数据转发到网页之前,服务器端代码包含此块:
PdfPKCS7 sgn = new PdfPKCS7(
null, chain, DigestAlgorithms.SHA256, true
);
byte[] preSigned = sgn.getAuthenticatedAttributeBytes(
hash, sap.SignDate, null, null, CryptoStandard.CMS
);
var hashedValue = Convert.ToBase64String(preSigned);
在手头的情况下,这不是必要的。仅当您使用的外部签名API仅返回已签名的摘要时才需要它;在这种情况下,PdfPKCS7
实例构建CMS / PKCS#7签名容器。另一方面,您使用您知道的API
CAPICOM文档说签署的响应采用PKCS#7格式。
因此,您不需要(更重要的是)不得使用 PdfPKCS7
实例。
服务器端hash
变量的内容已经是要签名的数据的哈希摘要值。因此,前端,即那里使用的sign.js,不得再次散列它以获取消息摘要属性值以放入签名。
但是IE的sign.js签名方法最终会执行
var signedData = new ActiveXObject("CAPICOM.SignedData");
// Set the data that we want to sign
signedData.Content = src;
另一方面, SignedData.Content
被记录为
内容 读/写要签名的数据。
因此来自后端的哈希用作要签名的数据而不是要签名的数据的哈希,你确实哈希两次所以有错误那里的哈希值。
因此,看起来你必须传输整个远程流,这不是真正实用的......
事实上,一些旧的iTextSharp(版本4.x)签名示例使用了CAPICOM。但该代码只有用,因为它创建了PDF签名类型 adbe.pkcs7.sha1 的签名,其中远程流的SHA1哈希确实是嵌入并由PKCS#7签名的数据签名
这不再是一个真正的选择,因为