You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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验证时,签名对应的内容与实际文档的认证属性不匹配,从而提示篡改。

修复步骤

  1. 生成CMS认证属性:通过pdfPKCS7.GetAuthenticatedAttributeBytes获取包含时间戳、OCSP等属性的字节数组,这才是需要签名的目标内容。
  2. 重新签名认证属性哈希:将认证属性的哈希发送给第三方服务,获取对应的PKCS1签名。
  3. 设置外部签名并生成PKCS7:将新的签名设置到PdfPKCS7中,再生成最终的带时间戳PKCS7签名。

关键修改点

  • 移除了直接使用原始文档哈希签名的逻辑,改为对CMS认证属性哈希签名。
  • 重新调用第三方签名服务处理认证属性哈希,确保签名内容与PKCS7结构匹配。
  • 修正了SetExternalDigest的传入参数为认证属性的签名,而非原始文档的签名。

内容的提问来源于stack exchange,提问作者Jóhann Hallgrimss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:59:49