iText 7 PKCS1转PKCS7签名问题:Adobe提示文档已篡改
问题描述
我在继承IExternalSignatureContainer的类中,通过第三方服务获取PKCS1签名,再生成带时间戳的PKCS7签名。但Adobe Reader提示“文档自签名后已被篡改或损坏”;若直接使用第三方返回的无时间戳PKCS7签名则可正常使用。
相关代码
public byte[] Sign(Stream data) { String hashAlgorithm = DigestAlgorithms.SHA256; byte[] hash = DigestAlgorithms.Digest(data, DigestAlgorithms.GetMessageDigest(hashAlgorithm)); data.Seek(0, SeekOrigin.Begin); string hashStr = Convert.ToBase64String(hash); SaveMetaData(hashStr); // 从认证服务获取签名 // 该类使用第三方签名服务对哈希签名,用户通过手机确认签名 // 此签名没有时间戳,但已验证正确 AudkenniSignature audkenniSign = new AudkenniSignature(this.Propperties, this.KtOrPhone, ConfirmString, usePKCS7); string authId = audkenniSign.InitSign(hashStr); audkenniSign.GetSignatureResult(authId); // 处理数据 var user = audkenniSign.UserInfo; SignerFullName = user.name; SignerIdentifier = user.nationalRegisterId; Dal.oResult.addWarning("Undirritari nafn", SignerFullName); Dal.oResult.addWarning("Undirritari kennitala", SignerIdentifier); Propperties.SignerIDNumber = SignerIdentifier; Propperties.SignerName = SignerFullName; // 如果直接返回此参数,签名正确但无时间戳,我们需要添加时间戳,因此继续后续步骤 // Signature = System.Convert.FromBase64String(audkenniSign.Signature); byte[] certBytes = Convert.FromBase64String(audkenniSign.SigningCert); X509Certificate2 certificate = new X509Certificate2(certBytes); X509Chain certificateChain = new X509Chain(); certificateChain.ChainPolicy = new X509ChainPolicy { RevocationMode = X509RevocationMode.Offline, // 若有OCSP响应器可改为Online RevocationFlag = X509RevocationFlag.EntireChain, }; bool chainBuilt = certificateChain.Build(certificate); UsedCerts = new X509Certificate2Collection(certificateChain.ChainElements.Cast<X509ChainElement>().Select(e => e.Certificate).ToArray()); SigningCertSubject = certificate.Subject; SignerCertExpier = certificate.NotAfter.ToShortDateString(); try { string[] arr = SigningCertSubject.Split(','); string name = arr.FirstOrDefault(x => x.Trim().StartsWith("CN")); string serial = arr.FirstOrDefault(x => x.Trim().StartsWith("SERIALNUMBER")); name = name.Contains('=') ? name.Split('=')[1] : name; serial = serial.Contains('=') ? serial.Split('=')[1] : serial; Dal.oResult.addWarning("Nafn ur subject", name); Dal.oResult.addWarning("Kennitala ur subjet", serial); } catch (InvalidOperationException e) { throw new Exception("Undirritunin er ólögleg", e); } // 转换证书链为BouncyCastle格式 X509CertificateParser parser = new X509CertificateParser(); Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[UsedCerts.Count]; for (int i = 0; i < UsedCerts.Count; i++) { chain[i] = parser.ReadCertificate(UsedCerts[i].GetRawCertData()); } ITSAClient tsaClient = new TSAClientBouncyCastle(Propperties.TimeServicePropperties.TimeServerURL); OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null); Collection<byte[]> ocsp = new Collection<byte[]>(); for (var i = 0; i < chain.Length - 1; i++) { byte[] encoded = ocspClient.GetEncoded((Org.BouncyCastle.X509.X509Certificate)chain[i], (Org.BouncyCastle.X509.X509Certificate)chain[i + 1], null);// Propperties.OcspURL if (encoded != null) ocsp.Add(encoded); } // 初始化PdfPKCS7对象 PdfPKCS7 pdfPKCS7 = new PdfPKCS7(null, chain, hashAlgorithm, true); // 获取CMS认证属性字节数组(包含时间戳、OCSP等属性) byte[] authenticatedAttributes = pdfPKCS7.GetAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, ocsp, null); // 计算认证属性的哈希 byte[] attrHash = DigestAlgorithms.Digest(new MemoryStream(authenticatedAttributes), DigestAlgorithms.GetMessageDigest(hashAlgorithm)); string attrHashStr = Convert.ToBase64String(attrHash); // 重新调用第三方服务对认证属性哈希签名 string attrAuthId = audkenniSign.InitSign(attrHashStr); audkenniSign.GetSignatureResult(attrAuthId); byte[] attrSignature = System.Convert.FromBase64String(audkenniSign.Signature); // 设置外部签名(认证属性的签名) pdfPKCS7.SetExternalDigest(attrSignature, null, "RSA"); // 生成带时间戳的PKCS7签名 var result = pdfPKCS7.GetEncodedPKCS7(hash, PdfSigner.CryptoStandard.CMS, tsaClient, ocsp, null); return result; }
问题原因与修复说明
核心问题
你当前的代码直接将原始文档哈希的PKCS1签名用于生成带时间戳的PKCS7,但实际上PKCS7签名需要签名的是包含时间戳、OCSP等认证属性的复合哈希,而非原始文档哈希。这导致Adobe验证时,签名对应的内容与实际文档的认证属性不匹配,从而提示篡改。
修复步骤
- 生成CMS认证属性:通过
pdfPKCS7.GetAuthenticatedAttributeBytes获取包含时间戳、OCSP等属性的字节数组,这才是需要签名的目标内容。 - 重新签名认证属性哈希:将认证属性的哈希发送给第三方服务,获取对应的PKCS1签名。
- 设置外部签名并生成PKCS7:将新的签名设置到
PdfPKCS7中,再生成最终的带时间戳PKCS7签名。
关键修改点
- 移除了直接使用原始文档哈希签名的逻辑,改为对CMS认证属性哈希签名。
- 重新调用第三方签名服务处理认证属性哈希,确保签名内容与PKCS7结构匹配。
- 修正了
SetExternalDigest的传入参数为认证属性的签名,而非原始文档的签名。
内容的提问来源于stack exchange,提问作者Jóhann Hallgrimss
相关产品推荐
相关产品推荐

