PDF数字签名代码验证失败但Adobe Reader可正常验证问题求助
PDF数字签名校验失败问题排查
问题背景
有一份经过数字签名的PDF文件,使用Adobe Reader打开可正常通过签名校验,但自行开发的校验代码始终验证失败,两种实现方案的具体情况如下:
第一版原生JDK实现
代码如下:
public static void main(String[] args) throws Exception { HashMap<String, Object> keyPair = new HashMap<>(); String password = "123"; KeyStore keyStore = KeyStore.getInstance("pkcs12"); //, "BC"); keyStore.load(new FileInputStream("/Users/shivam/Documents/Projects/label-processor/src/main/resources/DigitalSignatureFile.pfx"), password.toCharArray()); Enumeration<String> keyStoreAliasEnum = keyStore.aliases(); String alias = null; PublicKey publicKey = null; PrivateKey privateKey = null; byte[] signature = null; while ( keyStoreAliasEnum.hasMoreElements() ) { alias = keyStoreAliasEnum.nextElement(); if (password != null) { privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray()); X509Certificate x509Certificate = (X509Certificate) keyStore.getCertificate(alias); publicKey = x509Certificate.getPublicKey(); signature = x509Certificate.getSignature(); keyPair.put("Alias", alias); keyPair.put("PublicKey", publicKey); keyPair.put("PrivateKey", privateKey); keyPair.put("X509Certificate", x509Certificate); } } verify(publicKey, signature); } public static void verify(PublicKey publicKey, byte[] sigBytes) throws Exception { Signature sig = Signature.getInstance("SHA256withRSA"); sig.initVerify(publicKey); FileInputStream datafis = new FileInputStream("/Users/shivam/Desktop/DigitallySignedFile.pdf"); byte[] data = IOUtils.toByteArray(datafis); sig.update(data); boolean verifies = sig.verify(sigBytes); System.out.println("signature verifies: " + verifies); FileUtils.writeByteArrayToFile( new File("/Users/shivam/Documents/Projects/label-processor/src/main/resources/VerifiedFile.pdf"), data ); }
运行结果:输出signature verifies: false,导出的文件打开后显示签名未验证。
第二版iText库实现
代码如下:
public static void main(String[] args) throws Exception { byte[] pdfBytes = Files.readAllBytes( Paths.get("/Users/shivam/Desktop/DigitallySignedFile.pdf") ); PdfReader pdfReader = new PdfReader(pdfBytes); AcroFields acroFields = pdfReader.getAcroFields(); List<String> signatures = acroFields.getSignatureNames(); boolean valid = false; if (!signatures.isEmpty()) { for (String signature : signatures) { if (acroFields.signatureCoversWholeDocument(signature)) { PdfPKCS7 pkcs7 = acroFields.verifySignature(signature); valid = pkcs7.verify(); String reason = pkcs7.getReason(); Calendar signedAt = pkcs7.getSignDate(); X509Certificate x509Certificate = pkcs7.getSigningCertificate(); Principal issuerDN = x509Certificate.getIssuerDN(); Principal subjectDN = x509Certificate.getSubjectDN(); logger.info("valid = {}, date = {}, reason = '{}', issuer = '{}', subject = '{}'", valid, signedAt.getTime(), reason, issuerDN, subjectDN); break; } } } FileUtils.writeByteArrayToFile( new File("/Users/shivam/Documents/Projects/label-processor/src/main/resources/VerifiedFile.pdf"), pdfBytes ); }
运行结果:代码日志输出签名验证通过,但导出的文件打开后仍显示签名未验证。
问题原因分析&解决思路
第一版实现错误点
- 签名值来源错误:代码中用
x509Certificate.getSignature()获取到的是CA机构给证书本身做的签名,不是PDF文件中存储的对文档内容的签名值,PDF的签名内容存储在文档的AcroForm签名字段中。 - 校验范围错误:直接使用整个PDF文件的字节做哈希校验不符合PDF签名规范,签名生成时会排除签名字段中存储的签名值本身对应的字节,只对
ByteRange属性指定的分段字节范围做哈希,直接全文件计算哈希必然和签名时的哈希值不匹配。
第二版实现问题排查
- 文件一致性校验:先对比原始PDF和导出的
VerifiedFile.pdf的MD5或SHA哈希值,如果哈希不一致,说明文件写出环节被篡改:检查是否有其他代码修改了pdfBytes变量,或者文件写出工具类存在编码处理问题。如果哈希完全一致,说明导出文件和原文件完全相同,问题出在阅读器端:检查打开导出文件使用的阅读器版本、配置是否和打开原文件时一致,是否开启了离线模式导致无法完成证书吊销校验等操作。 - 校验逻辑对齐:iText的
pkcs7.verify()默认只校验签名的摘要完整性和签名有效性,不会校验证书信任链、有效期、吊销状态,如果需要和Adobe Reader的校验逻辑完全对齐,需要补充证书链校验逻辑,传入系统信任根证书库,对签名证书的有效性做完整校验。
内容的提问来源于stack exchange,提问作者Shivam Singh
相关产品推荐
相关产品推荐

