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

iTextPDF 5.5.13.3外部签名无效:PDF被修改报错求助

iTextPDF 5.5.13.3 外部签名无效问题排查

核心问题分析

你的代码中导致签名验证提示“PDF已被修改”的关键原因是待签属性计算与最终签名使用的证书链不一致,另外存在输入流重复读取的冗余操作,具体如下:

  • 待签属性与签名的证书链不匹配
    在EmptyContainer的sign方法中,你使用Certificate[] certs = {null};构建了空证书链的PdfPKCS7实例来计算待签属性toSign,但后续createSignature方法中使用的是真实用户证书链的PdfPKCS7实例生成CMS签名。待签属性(Authenticated Attributes)包含证书相关信息,两次使用不同的证书链会导致验证时待签属性与签名内容不匹配,阅读器判定文档已被修改。

  • 输入流重复读取的冗余操作
    EmptyContainer的sign方法中,先通过DigestAlgorithms.digest(is, ...)读取输入流计算文档哈希hash,随后又调用DigestUtils.sha256Hex(is)再次读取已耗尽的输入流,虽然该结果未被使用,但会导致输入流指针处于末尾,若后续有依赖输入流的逻辑会出错。

  • 证书链不完整(潜在问题)
    setChain方法仅加载了单个用户证书,未包含中间证书和根证书。若你的证书是CA签发的,缺少完整信任链会导致签名无法通过身份验证,虽不是当前“已修改”提示的直接原因,但会影响签名整体有效性。

修复方案

  1. 统一待签属性计算与签名的证书链
    修改EmptyContainer的sign方法,使用真实的chain数组而非空证书链:

    public byte[] sign(InputStream is) {
        try {
            ExternalDigest digest = getDigest();
            // 替换空证书链为真实用户证书链
            Certificate[] certs = chain;
    
            hash = DigestAlgorithms.digest(is, digest.getMessageDigest("SHA256"));
            // 移除冗余的输入流读取操作
            // String temp = DigestUtils.sha256Hex(is);
    
            PdfPKCS7 sgn = getPkcs(certs);
    
            toSign = sgn.getAuthenticatedAttributeBytes(hash, getOscp(), null,
                MakeSignature.CryptoStandard.CMS);
    
            return new byte[0];
        } catch (IOException | GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }
    
  2. 确保客户端签名流程正确
    客户端需要对toSign字节数组进行如下操作:

    • 计算toSign的SHA256哈希值
    • 使用用户私钥对该哈希值进行RSA签名
    • 将签名结果传入createSignature的signature参数
  3. 补充完整证书链
    修改setChain方法,加载包含用户证书、中间证书及根证书的完整证书链,例如从PKCS#12文件加载(若使用PEM格式,需依次加载所有证书):

    public static void setChain(InputStream userCertificate, InputStream intermediateCert, InputStream rootCert) throws CertificateException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        Certificate userCert = cf.generateCertificate(userCertificate);
        Certificate intermediate = cf.generateCertificate(intermediateCert);
        Certificate root = cf.generateCertificate(rootCert);
        chain = new Certificate[]{userCert, intermediate, root};
    }
    

验证要点

修复后重新测试,确认:

  • 待签属性toSign的计算与最终CMS签名使用相同的证书链
  • 客户端签名的是toSign的SHA256哈希值
  • PDF阅读器验证签名时,无“已被修改”提示,且身份验证通过

内容的提问来源于stack exchange,提问作者Andrea

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 17:41:04