需要通过使用外部Web服务对文档哈希进行签名来对PDF进行签名,该过程必须分两步完成,并使用一个临时的空签名。
继What's the best practice to keep all the constants in Flutter?和Priyanka question并阅读该帖子的mkl答案后,即使添加了像Grazina这样的哈希前缀,我当前的签名也无效。
iTextSharp版本:5.5.13.1
该程序是Grazina question的另一种方法。 当前代码(编译并开始调用 SignPDF 方法):
public class PDFSigner
{
private const string SIG_FIELD_NAME = "sigField1";
private void SignPDF(string pdfFilePath, string userId)
{
var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";
var signedPdfFilePath = $"{pdfFilePath}.signed.pdf";
//Get certificates chain from webservice
var certificatesChain = this.GetUserCertificates(userId);
byte[] hash = this.CreatePDFEmtySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);
//Get signature from webservice
byte[] signedHash = this.GetSignature(hash, userId);
CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);
}
private byte[] CreatePDFEmtySignature(string pdfFilePath, string preparedSigPdfFilePath, List<Org.BouncyCastle.X509.X509Certificate> certificatesChain)
{
byte[] hash = null;
using (PdfReader reader = new PdfReader(pdfFilePath))
{
using (FileStream baos = File.OpenWrite(preparedSigPdfFilePath))
{
PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, SIG_FIELD_NAME);
//TODO: check how to select the correct certificate, have 3 items on list, selected leaf after debug (first one)
sap.Certificate = certificatesChain.First();
var externalEmptySigContainer = new MyExternalEmptySignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED, preparedSigPdfFilePath);
MakeSignature.SignExternalContainer(sap, (IExternalSignatureContainer)externalEmptySigContainer, 8192);
hash = externalEmptySigContainer.PdfHash;
}
}
return hash;
}
private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath,
byte[] hash, byte[] signedHash, List<Org.BouncyCastle.X509.X509Certificate> certificatesChain)
{
using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
{
using (FileStream baos = File.OpenWrite(signedPdfFilePath))
{
IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(signedPdfFilePath, 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 MyExternalEmptySignatureContainer(PdfName filter, PdfName subFilter, string pdfTempFilePath) : base(filter, subFilter)
{
this.PdfTempFilePath = pdfTempFilePath;
}
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 = $"{this.PdfTempFilePath}.messageHash-b64.txt";
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 = $"{this.PdfTempFilePath}.messageHash-with-prefix-b64.txt";
File.WriteAllText(messageHashWithPrefixFilePath, Convert.ToBase64String(digestInfo));
#endregion Log
this.PdfHash = digestInfo;
return sigContainer;
}
}
public class MyExternalSignatureContainer : IExternalSignatureContainer
{
public byte[] Hash { get; set; }
public byte[] SignedHash { get; set; }
public List<Org.BouncyCastle.X509.X509Certificate> CertificatesList { get; set; }
public MyExternalSignatureContainer(string signedPdfFilePath, byte[] hash, byte[] signedHash, List<Org.BouncyCastle.X509.X509Certificate> certificatesList)
{
this.Hash = hash;
this.SignedHash = signedHash;
this.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) { }
}
public byte[] GetSignature(byte[] hash, string userId)
{
// Request signature for hash value messageHash and return signature bytes
byte[] signature = null;
//CALL WEBSERVICE:
//signature = WEBSERVICE_CALL_TO_GET_SIGNED_HASH(hash, userId);
return signature;
}
private List<Org.BouncyCastle.X509.X509Certificate> GetUserCertificates(string userId)
{
List<Org.BouncyCastle.X509.X509Certificate> certChain = null;
//CALL WEBSERVICE:
//certChain = WEBSERVICE_CALL_TO_GET_SIGNED_CERTIFICATES(userId);
return certChain;
}
}
获得结果
邮件哈希(BASE 64):
lA2cMByHLkuNdd+aHJRDy3GD2VIeIpVtzlgQGsq3cJw=
带前缀的邮件哈希(BASE 64):
MDEwDQYJYIZIAWUDBAIBBQAEIJQNnDAchy5LjXXfmhyUQ8txg9lSHiKVbc5YEBrKt3Cc
签名哈希(基于64):
LURoF4w3H7uwR3xltjZTBbxBlTCCyD5AqVfseg9F1jn9lfnJ4KAqDL85s2ABSN7iieqjhUd0/U7fReT8gmRV5ZVyjGZcA4BaXr9Lx5E8vLerrHfbE3lsqb4Qm4/3oWX7BjNjfK4ptrBLIaYiDW28sxRKev5mdoo9W2ecIPWAaD8wyrKG/sXj62FQsmetdB0Rzd5rPNbsjVhOeei2V1g1PgF7evJZAz6+1smIWHXPgpxQJ8gZG6KcnHy8N43TGxQ0yV6DKqpl5DGEgqDwiXUY2kGglYNkdaS/5bQy941j7AyEDulni8YXtQ+XH2opuq1OkqVPipLqQnk3DYMPQUzjWqatI1Awfhv4fnceZ2djxgpgtv03tM5PzpHmelXr1gGfcChNDA603SJr+9XVok35mslx13kv+03M4aa2Myp4JKPSNQBuqdeiXKMsXilgv1M13xdbaFL35Omq9ciQbts4kRPpeLj+9PC+kHsyrerRO8pSxHcEjojPqTdYT+pWAmlU
更新 使用my previous question测试了PDF签名,获得了以下结果:
Certificates:
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 7590871326079402939
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 1738456118788016053
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 8957244856106358046
Attribute Certificates: none
CRLs: none
SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]
Signed attribute 1.2.840.113549.1.9.4 (PKCS 9 - Message Digest)
Digest:
3031300D060960864801650304020105000420940D9C301C872E4B8D75DF9A1
C9443CB7183D9521E22956DCE58101ACAB7709C
Signed attribute 1.2.840.113549.1.9.3 (PKCS 9 - Content Type)
Signed Attributes Hash:
08767823328F202C1C3E5DB543785ED591C6D84D23DAF3DCBB83684B987008CB
Signed Attributes Hash Hash:
1E2D10B23CD772D16987126182E51BD4D827DB58C497BA4129BB533A576E3548
!!! Decrypted RSA signature is not PKCS1 padded: Decryption error
Decrypted signature bytes: [REMOVED]
!!! Decrypted RSA signature does not end with the PSS 0xbc byte either
!!! Signature does not validate with certificate
将尝试将填充添加到签名中,不胜感激。
答案 0 :(得分:1)
首先,根据结果PDF ,尽管使用了GetUserCertificates
,但似乎没有返回GetSignature
的签名的签名者证书相同的userId
值。
这可以通过粘贴到问题中的AnalyzeSignatures
输出来表明:
SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]
签名容器中单个SignerInfo
中的SID与所包含的证书集中的单个证书匹配。
!!! Decrypted RSA signature is not PKCS1 padded: Decryption error
Decrypted signature bytes: [REMOVED]
!!! Decrypted RSA signature does not end with the PSS 0xbc byte either
该证书的公共密钥是RSA密钥,并且SignerInfo
签名值的长度与密钥长度匹配,但是使用该密钥解密该值既不会返回填充了PKCS#1 v1.5的内容,也不会返回PSS结构。因此,“签名值”不是根本不是RSA签名值,或者是使用与所称签名者证书中的公钥不匹配的私钥生成的签名。
因此,要做的第一件事就是分析
//CALL WEBSERVICE:
//signature = WEBSERVICE_CALL_TO_GET_SIGNED_HASH(hash, userId);
和
//CALL WEBSERVICE:
//certChain = WEBSERVICE_CALL_TO_GET_SIGNED_CERTIFICATES(userId);
代码的隐藏部分并进行修复(如果存在问题,则修复Web服务),然后将其固定集成到PDF签名框中。
此处的PDF签名框架包含一些错误。上一个问题"External signing PDF with iText (2) [closed]"中的代码看起来更正确,因此在这里找到对签名Web服务的正确使用之后,我建议在该代码的基础上采用下一种方法。
尽管如此,这里还是对两个错误的解释,这些错误很快引起了我的注意:
byte[] hash = this.CreatePDFEmtySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);
//Get signature from webservice
byte[] signedHash = this.GetSignature(hash, userId);
CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);
CreatePDFEmtySignature
返回的哈希值是类MyExternalEmptySignatureContainer
试图确定有符号字节范围的哈希值的结果。
但是通常在CMS签名容器的上下文中(最原始的类型除外),不是直接为文档字节创建实际的签名字节,而是为属性结构(即所谓的“签名属性”或“经过身份验证的”)创建属性”);文档字节的哈希仅仅是这些属性之一的值。
因此,signedHash
在hash
中通过GetSignature
返回的CreateFinalSignature
的{{1}}的签名值MyExternalSignatureContainer.Sign
被注入到PKCS#7 / CMS签名容器SignerInfo中其经过身份验证的属性具有完全不同的哈希值。
使用iText PdfPKCS7
类生成签名容器时,必须使用与后面的{{ 1}}通话。
因此,对这样签名的PDF的实际签名字节的验证必须失败。
由PdfPKCS7.GetAuthenticatedAttributeBytes
返回的哈希值包装在PdfPKCS7.GetEncodedPKCS7
结构中(通过在CreatePDFEmtySignature
中相应地预先放置一些字节)。
通过DigestInfo
中的MyExternalEmptySignatureContainer.Sign
,稍后将其作为有符号字节的哈希值提供给CreateFinalSignature
。但是,这里期望它是裸露的,没有包裹在MyExternalSignatureContainer.Sign
结构中。
因此,对于像这样签名的PDF,签名文档字节的哈希值验证必须失败。