如何用iText验证PDF签名的文档哈希、证书有效性及篡改情况?
PDF签名验证完整实现(基于iText)
核心逻辑梳理
PDF签名验证本质是三个核心环节:证书有效性校验、签名哈希与文档完整性匹配、签名合法性验证。你找不到待验证哈希的原因是:哈希并非直接存储在/Sig字典中,而是通过/ByteRange指定的文档片段计算得出,再与PKCS#7签名中的摘要做比对。
一、证书有效性验证(PKCS#7方式)
用iText的PdfPKCS7类直接解析签名内容,完成证书链有效性校验:
PdfReader reader = new PdfReader("signed.pdf"); AcroFields acroFields = reader.getAcroFields(); List<String> signNames = acroFields.getSignatureNames(); for (String signName : signNames) { // 获取签名对应的PKCS7对象 PdfPKCS7 pkcs7 = acroFields.verifySignature(signName); // 验证证书链(需传入信任证书库,这里示例用自定义信任存储) boolean certValid = pkcs7.verifyCertificates(new TrustedCertStore().getKeystore(), null, null); // 输出证书状态与详情 System.out.println("证书有效性:" + certValid); X509Certificate signCert = pkcs7.getSigningCertificate(); System.out.println("签名证书主体:" + signCert.getSubjectDN()); }
二、签名哈希与文档完整性校验
关键原理:/ByteRange的作用
/Sig字典中的/ByteRange是四元素数组(如[0, 1000, 2000, 500]),表示签名时仅对0到1000字节和2000到2500字节的内容做哈希,中间1000-2000字节是签名内容(/Contents),签名时会被跳过。我们需要计算这两段内容的哈希,再与PKCS7中的签名摘要比对。
代码实现
for (String signName : signNames) { PdfPKCS7 pkcs7 = acroFields.verifySignature(signName); PdfDictionary sigDict = acroFields.getSignatureDictionary(signName); // 获取ByteRange数组 PdfArray byteRange = sigDict.getAsArray(PdfName.BYTERANGE); long[] ranges = byteRange.toLongArray(); // 初始化哈希算法(与签名算法一致) MessageDigest digest = MessageDigest.getInstance(pkcs7.getDigestAlgorithm()); RandomAccessFile raf = new RandomAccessFile("signed.pdf", "r"); // 计算第一段内容的哈希 raf.seek(ranges[0]); byte[] buffer = new byte[8192]; long remaining = ranges[1]; while (remaining > 0) { int read = raf.read(buffer, 0, (int) Math.min(remaining, buffer.length)); digest.update(buffer, 0, read); remaining -= read; } // 计算第二段内容的哈希 raf.seek(ranges[2]); remaining = ranges[3]; while (remaining > 0) { int read = raf.read(buffer, 0, (int) Math.min(remaining, buffer.length)); digest.update(buffer, 0, read); remaining -= read; } raf.close(); // 比对计算哈希与签名摘要 byte[] computedHash = digest.digest(); byte[] signedHash = pkcs7.getEncodedPKCS1(); boolean isTampered = !Arrays.equals(computedHash, signedHash); System.out.println("文档是否被篡改:" + isTampered); }
三、正确获取/Sig字典的方式
直接通过AcroFields的签名字段获取,避免无效操作:
PdfDictionary sigDict = acroFields.getSignatureDictionary(signName); // 读取字典中指定字段 PdfName filter = sigDict.getAsName(PdfName.FILTER); PdfDate signDate = sigDict.getAsDate(PdfName.M); // ...其他字段读取逻辑
注意事项
- 哈希算法必须与签名时使用的一致,可通过
pkcs7.getDigestAlgorithm()获取 - 证书验证需配置信任证书库,否则无法判断证书是否来自可信机构
- 若PDF含时间戳签名,需额外验证时间戳的有效性
内容的提问来源于stack exchange,提问作者tulak.hord
相关产品推荐
相关产品推荐

