首先,虽然我关注StackOverflow已经有相当一段时间了,但这是我第一次发布一些东西,所以如果我做错了或者不按规则做的话,请随时为我指出正确的方向。
我正在开发一个PDF数字签名应用程序,使用iText5,它依赖于一个外部服务,在我准备好PDF签名后提供一个签名哈希。
如iText文档中所述,在第一阶段,我准备了PDF(在最终实现中,所有PDF都可能是多签名的,因此我使用追加模式),如下所示:
public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, List<Org.BouncyCastle.X509.X509Certificate> certificateChain) {
// we create a reader and a stamper
using (PdfReader reader = new PdfReader(unsignedPdf)) {
using (FileStream baos = File.OpenWrite(tempPdf)) {
List<Org.BouncyCastle.X509.X509Certificate> chain = certificateChain;
PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
sap = pdfStamper.SignatureAppearance;
sap.Certificate = certificateChain[0];
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, signatureFieldName);
//sap.SetVisibleSignature(signatureFieldName);
sap.SignDate = DateTime.Now;
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.Date = new PdfDate(sap.SignDate);
dic.Name = CertificateInfo.GetSubjectFields(chain[0]).GetField("CN");
sap.CryptoDictionary = dic;
sap.Certificate = certificateChain[0];
sap.Acro6Layers = true;
sap.Reason = "test";
sap.Location = "test";
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(sap, external, 8192);
signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
byte[] hash = DigestAlgorithms.Digest(sap.GetRangeStream(), "SHA256");
//byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
return hash;
}
}
}
在这一步之后,我将哈希发送给外部服务,外部服务返回一个签名哈希。
检查我发送给服务的散列,它似乎是正确的,因为它覆盖了除新签名内容之外的所有PDF。
然后我使用以下方法结束签名过程:
private byte[] Sign(PdfPKCS7 signatureContainer, List<X509Certificate2> chain2, List<Org.BouncyCastle.X509.X509Certificate> chain, byte[] hash, byte[] signedBytes, string tmpPdf, string signedPdf, string signatureFieldName) {
System.Security.Cryptography.RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = chain2[0].PublicKey.Key as System.Security.Cryptography.RSACryptoServiceProvider;
bool verify = publicCertifiedRSACryptoServiceProvider.VerifyHash(hash, "SHA256", signedBytes); //verify if the computed hash is same as signed hash using the cert public key
Console.WriteLine("PKey signed computed hash is equal to signed hash: " + verify);
AsnEncodedData asnEncodedData = new AsnEncodedData(signedBytes);
Console.WriteLine(asnEncodedData.Format(true));
//ITEXT5
try {
//Console.WriteLine("Signed bytes: " + Encoding.UTF8.GetString(signedBytes));
using (PdfReader reader = new PdfReader(tmpPdf)) {
using (FileStream outputStream = File.OpenWrite(signedPdf)) {
IExternalSignatureContainer external = new Objects.MyExternalSignatureContainer(signedBytes, chain, signatureContainer);
MakeSignature.SignDeferred(reader, signatureFieldName, outputStream, external);
}
}
return new byte[] { };
}
catch(Exception ex) {
File.Delete(tmpPdf);
Console.WriteLine("Error signing file: " + ex.Message);
return new byte[] { };
}
}
MyExternalSignatureContainer代码:
public class MyExternalSignatureContainer : IExternalSignatureContainer {
private readonly byte[] signedBytes;
public List<Org.BouncyCastle.X509.X509Certificate> Chain;
private PdfPKCS7 sigField;
public MyExternalSignatureContainer(byte[] signedBytes) {
this.signedBytes = signedBytes;
}
public MyExternalSignatureContainer(byte[] signedBytes, List<Org.BouncyCastle.X509.X509Certificate> chain, PdfPKCS7 pdfPKCS7) {
this.signedBytes = signedBytes;
this.Chain = chain;
this.sigField = pdfPKCS7;
}
public byte[] Sign(Stream data) {
try {
sigField.SetExternalDigest(signedBytes, null, "RSA");
return sigField.GetEncodedPKCS7(signedBytes, null, null, null, CryptoStandard.CMS);
}
catch (IOException ioe) {
throw ioe;
}
}
public void ModifySigningDictionary(PdfDictionary signDic) {
}
}
问题是当我在Acrobat中打开PDF时,它声明自签名应用以来文档已经被修改或损坏。
(如果我在PDF-XChange中打开相同的PDF,它会显示该PDF未被修改)。
就像StackOverlow中关于同一问题的另一篇文章中所说的那样(我无法找到链接它的文章),一个潜在的问题将是为临时文件使用不同的流。我已经尝试过使用同样的流,但也没有运气。
PDF示例:
原始文件
发送到服务得Base64哈希:
<代码>XYFAS/SISA/TK5HCL035RPBJOCZRH9E5RGIAMPQGKJI=
已发送Base64签名哈希响应:
MKL回答后更新:
根据答案,我只在一个阶段就更改了文档的代码签名,最后使用了以下方法:
using (PdfReader reader = new PdfReader(fileLocation)) {
using (FileStream baos = File.OpenWrite(tmpFile)) {
List<Org.BouncyCastle.X509.X509Certificate> chain = Chain;
PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
sap.Certificate = Chain[0];
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, signatureFieldName);
//sap.SetVisibleSignature(signatureFieldName);
sap.SignDate = DateTime.Now;
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.Date = new PdfDate(sap.SignDate);
dic.Name = CertificateInfo.GetSubjectFields(chain[0]).GetField("CN");
sap.CryptoDictionary = dic;
sap.Certificate = Chain[0];
sap.Acro6Layers = true;
//sap.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS;
sap.Reason = "test";
sap.Location = "test";
IExternalSignature signature = new Objects.RemoteSignature(client, signatureRequest);
MakeSignature.SignDetached(sap, signature, Chain, null, null, null, 8192, CryptoStandard.CMS);
}
}
和IExternalSignature实现:
public virtual byte[] Sign(byte[] message) {
IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
//
// Request signature for hash value messageHash
// and return signature bytes
//
signatureRequest.Hash = messageHash;
SignatureService.SignatureResponse signatureResponse = client.Signature(signatureRequest);
if (signatureResponse.Status.Code == "00") {
return signatureResponse.DocumentSignature;
}
else {
throw new Exception("Error signing file: " + signatureResponse.Status.Message);
}
}
虽然我知道当前的证书是无效的,但它是由服务提供的,在服务的以前实现中,我将发送整个PDF进行签名,签名的PDF也是用这个证书签名的。
一个问题:知道在一个两阶段的签名中,我能够用这个证书签署PDF(除了签名错误后被修改或损坏的文档),这个方法不应该也适用于同一个证书吗?
目前正在发生的情况是:
检查签名:
同样,如果我在PDF-XChange中打开相同的PDF,签名是有效的,文档没有被修改。要求是PDF在Acrobat中是有效的,但我对读者之间的这种差异感到困惑。
结果PDF
在将这个SHA256前缀添加到消息摘要之后,得到的PDF现在已经正确签名了。
Adobe Reader是否接受固定签名?
我怀疑.签名者证书的密钥用法仅包含用于对其他证书进行签名的值。
问:在我最初的代码中,我有一个两阶段的签名。在单符号方法中应用的相同原则是否仍然有效,即应用SHA256前缀执行预签名字节,并在使用结果的有符号字节设置摘要之后?
您的代码中有许多问题。
首先,您的代码混合了不同代的iText签名API。有较老的API一代,它要求您非常接近PDF内部,有较新的API(自5.3.x版本以来),它作为较老的API的一个层实现,并且不要求您了解这些内部。
“PDF文档的数字签名”白皮书重点展示了较新的API,只有4.3.3节“使用客户端上创建的签名在服务器上签署文档”使用了旧的API,因为用例不允许使用较新的API。
(在某些情况下,可以混合使用这些API,但您应该真正知道自己在做什么,但仍然可能会出错……)
但现在一些更具体的问题:
Makesignature.Sign*
方法隐式关闭基础的PDFStamper
和SignatureOutcome
对象,因此不应假定此后使用这些对象会产生合理的信息。
MakeSignature.SignExternalContainer(sap, external, 8192);
signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
byte[] hash = DigestAlgorithms.Digest(sap.GetRangeStream(), "SHA256");
因此,sap.getRangeStream()
可能返回错误的东西。(它可能仍然返回正确的数据,但您不应该指望这一点。)
GetBytestosign
返回签名的PDF文档范围的哈希值:
signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
byte[] hash = DigestAlgorithms.Digest(sap.GetRangeStream(), "SHA256");
//byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
return hash;
但是,稍后,您的代码接受该返回值,对其签名,并尝试将返回的签名字节嵌入到pdfpkcs7
签名容器中。这是错误的,必须为签名容器的签名者信息的身份验证属性创建签名字节,而不是为文档哈希创建签名字节。
(顺便说一下,在这里,您使用了旧的签名API,但没有理解它,因此,使用它是错误的。)
在MyExternalSignatureContainer
中,您在两次调用中使用签名字节:
sigField.SetExternalDigest(signedBytes, null, "RSA");
return sigField.GetEncodedPKCS7(signedBytes, null, null, null, CryptoStandard.CMS);
第一个呼叫是正确的,他们属于这里。但是,在第二个调用中,应该使用签名文档范围的原始哈希值。
(在这里,您又一次使用了旧的签名API,但没有理解它,又一次错误地使用了它。)
分析您的示例PDF,您似乎将错误的证书声明为签名者证书。我这么认为是因为
首先,如果我正确理解您的意思,您从其他服务器请求签名,而其他服务器的反应很快,因此在等待签名时不需要释放所有资源。在这种情况下,不需要两个阶段的签名过程,您应该一步到位。您所需要的只是一个自定义的iExternalSignature
实现,类似于
class RemoteSignature : IExternalSignature
{
public virtual byte[] Sign(byte[] message) {
IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
//
// Request signature for hash value messageHash
// and return signature bytes
//
return CALL_YOUR_SERVICE_FOR_SIGNATURE_OF_HASH(messageHash);
}
public virtual String GetHashAlgorithm() {
return "SHA-256";
}
public virtual String GetEncryptionAlgorithm() {
return "RSA";
}
}
并像这样使用它进行签名:
PdfReader reader = new PdfReader(...);
PdfStamper pdfStamper = PdfStamper.CreateSignature(...);
PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
// set sap properties for signing
IExternalSignature signature = new RemoteSignature();
MakeSignature.SignDetached(sap, signature, chain, null, null, null, 0, CryptoStandard.CMS);
在更新您的问题,您添加了一个PDF签名与以上应用的更改。通过分析签名容器中的签名字节,我们可以清楚地看到,您的签名服务被设计成非常哑的,它应用PKCS1 v1.5填充和RSA加密,但它假定其输入已经打包到digestinfo
结构中。根据我的经验,这是一个不常见的假设,您应该告诉您的签名提供者正确地记录这一点。
对于您的代码,这意味着您必须将散列打包到digestinfo
结构中,然后再将其发送到服务。
RFC 8017第9.2节注释1解释了一种简单的方法:
对于附录B.1中提到的九个哈希函数,digestinfo
值的DER编码T等于:
...
SHA-256: (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 || H.
...
class RemoteSignature : IExternalSignature
{
public virtual byte[] Sign(byte[] message) {
IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
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);
//
// Request signature for DigestInfo value digestInfo
// and return signature bytes
//
return CALL_YOUR_SERVICE_FOR_SIGNATURE_OF_DIGEST_INFO(digestInfo);
}
public virtual String GetHashAlgorithm() {
return "SHA-256";
}
public virtual String GetEncryptionAlgorithm() {
return "RSA";
}
}
我怀疑.签名者证书的密钥用法仅包含用于对其他证书进行签名的值。
如果查看Adobe数字签名指南,您将看到有效的密钥用法扩展包括
不可否认
SignTransaction
(仅11.1.09)数字签名
(11.0.10及更高版本)第一个选项是不可行的,因为PDF的签名没有时间戳(这是一个非常重要的必要条件),所以选择了第二个选项。 这是我们的代码,首先,我们得到签名外观,并计算散列: 在这一点上,我们有了文档的哈希代码。然后我们将散列发送到webservice,我们得到签名的散列代码。 按照mkl的建议,我遵循了这本书的4.3.3部分PDF文档的数字签名,现在我的代码如下: 第一部分,当我们计算散列时: 在第二部分,我们得
我已经玩弄了一段时间的iTextSharp 5.5.7,找不到正确的方法从智能卡为PDF制作一个有效的数字签名-Adobe Reader总是说它的签名人和未知,并且不能解码签名的DER数据。 我查看了Makesignature.cs代码以供参考,以及is的功能: 然后,根据iExternalSignature.cs中的“Sign”方法 “@param message要进行散列和签名的邮件” 所以我
我有一个pdf文件,需要数字签名,签名是由外部服务提供。在启动签名之前,我没有证书链。我尝试了下面的代码,但是得到了Sigdict/Contents非法的数据消息。 创建文档哈希并将其发布到外部服务以对其进行签名的源 方法getPKCS7DataFromDigitalSignatureResponse(responseXML)用于解析外部服务响应,它将PKCS7作为字符串返回。我试图找出问题的根源
我正在使用iText 5 Java进行外部签名。首先,我创建签名外观,计算签名属性的哈希值,并为签名保留空白位置。稍后,当我从客户端获得签名的散列时,我通过。 这是用于签名的代码。我已经删除了很多功能代码,以保持代码的基本功能。 这是原始PDF。 这是空签名的PDF。 这是带有最终签名的PDF。 计算PDF的哈希值。 这是我从客户端收到的签名哈希签名。 我在stackoverflow上做了很多尝试
问题内容: 如何使用iText签名pdf?我正在通过此LINK进行操作, 但不了解my_private_key.pfx。我真的需要数字签名证书吗?请澄清一下。提前致谢。 问题答案: 您在问题中提到的文档很好。您必须创建数字签名文件。 该链接具有使用PKCS文件和签署PDF文档的工具。它声称使用iText,因此您应该能够理解这些步骤。源代码在这里
我正在尝试使用iText(sharp,5.5.13版)创建自定义数字签名,用户可以从四个位置(顶部、底部、左侧和右侧)设置图像位置,如下所示: 刚度: 左: 顶部: 底部: 到目前为止,我试着处理签名的第0层,但我认为我做得不对,因为签名细节是在第2层设置的。 然而,这只是设置图像位置的初始草图。在下面的代码中,我加载图像并将其放入一个块中(想法取自此示例) 结果或多或少是预期的,但有两个问题:签