iTextPDF 5.5.13.3外部签名无效:PDF被修改报错求助
核心问题分析
你的代码中导致签名验证提示“PDF已被修改”的关键原因是待签属性计算与最终签名使用的证书链不一致,另外存在输入流重复读取的冗余操作,具体如下:
待签属性与签名的证书链不匹配
在EmptyContainer的sign方法中,你使用Certificate[] certs = {null};构建了空证书链的PdfPKCS7实例来计算待签属性toSign,但后续createSignature方法中使用的是真实用户证书链的PdfPKCS7实例生成CMS签名。待签属性(Authenticated Attributes)包含证书相关信息,两次使用不同的证书链会导致验证时待签属性与签名内容不匹配,阅读器判定文档已被修改。输入流重复读取的冗余操作
EmptyContainer的sign方法中,先通过DigestAlgorithms.digest(is, ...)读取输入流计算文档哈希hash,随后又调用DigestUtils.sha256Hex(is)再次读取已耗尽的输入流,虽然该结果未被使用,但会导致输入流指针处于末尾,若后续有依赖输入流的逻辑会出错。证书链不完整(潜在问题)
setChain方法仅加载了单个用户证书,未包含中间证书和根证书。若你的证书是CA签发的,缺少完整信任链会导致签名无法通过身份验证,虽不是当前“已修改”提示的直接原因,但会影响签名整体有效性。
修复方案
统一待签属性计算与签名的证书链
修改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); } }确保客户端签名流程正确
客户端需要对toSign字节数组进行如下操作:- 计算
toSign的SHA256哈希值 - 使用用户私钥对该哈希值进行RSA签名
- 将签名结果传入
createSignature的signature参数
- 计算
补充完整证书链
修改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

