环境需求
- bouncycastle provider
1.60以上
- itext7-core
7.1.6
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.1.6</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.64</version>
</dependency>
Quick Start
在新的 bouncycastle 库中已经完全支持了 国密算法,而在itext中对国密算法的支持不太友好。
我们通过下面代码来验证PDF中的PKCS#7类型的数字签名
Security.addProvider(new BouncyCastleProvider());
String file = "C:/Users/Administrator/Documents/sm2pkcs7sig.pdf";
PdfReader reader = new PdfReader(file);
// 创建签名工具
PdfDocument pdfDocument = new PdfDocument(reader);
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
// 获取所有的签名域,域名称列表
List<String> fieldNames = signatureUtil.getSignatureNames();
for (String fieldName : fieldNames) {
// 获取签名值的PKCS#7数据
PdfPKCS7 pkcs7 = signatureUtil.readSignatureData(fieldName, "BC");
// 数字签名验证是否有效
boolean isValidate = pkcs7.verifySignatureIntegrityAndAuthenticity();
System.out.println("Is signature validate: " + isValidate);
}
C:/Users/Administrator/Documents/sm2pkcs7sig.pdf
是一个采用了SM3withSM2
签名的PDF文件
通过上述方法我们在验证类似于SHA1withRSA
、SHA256withRSA
的时候是OK,但是在验证国密算法时会抛出下面异常:
Caused by: com.itextpdf.kernel.PdfException: Unknown PdfException.
...
Caused by: java.security.NoSuchAlgorithmException: no such algorithm: 1.2.156.10197.1.401with1.2.156.10197.1.501 for provider BC
...
1.2.156.10197.1.401with1.2.156.10197.1.501
实际上就是 SM3withSM2
的OID
实际上 bouncycastle 库中是支持SM3withSM2
的OID的算法的,造成这个问题的原因是出在iText
上的。
查看 iText7源码 可以知道解析签名算法的方式是从pkcs#7对象中取出:摘要算法、加密算法的OID,然后通过JCE算法提供者,获取对应的签名算法,用于之后的验证
private Signature initSignature(PublicKey key) throws ...
String digestAlgorithm = getDigestAlgorithm();
if (PdfName.Adbe_x509_rsa_sha1.equals(getFilterSubtype()))
digestAlgorithm = "SHA1withRSA";
Signature signature = SignUtils.getSignatureHelper(digestAlgorithm, provider);
signature.initVerify(key);
return signature;
}
public String getDigestAlgorithm() {
return getHashAlgorithm() + "with" + getEncryptionAlgorithm();
}
public String getHashAlgorithm() {
return DigestAlgorithms.getDigest(digestAlgorithmOid);
}
public String getEncryptionAlgorithm() {
String encryptAlgo = EncryptionAlgorithms.getAlgorithm(digestEncryptionAlgorithmOid);
if (encryptAlgo == null)
encryptAlgo = digestEncryptionAlgorithmOid;
return encryptAlgo;
}
出现之所以getDigestAlgorithm
返还了1.2.156.10197.1.401with1.2.156.10197.1.501
而不是 SM3withSM2
这样的名字。
原因是:itext中EncryptionAlgorithms
和 DigestAlgorithms
类的映射关系中没有国密算法的标识符
那么为了简单的解决问题我们直接通过反射加入然后测试是否能够解决
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
Field digestNamesField = DigestAlgorithms.class.getDeclaredField("digestNames");
digestNamesField.setAccessible(true);
HashMap<String, String> digestNames = (HashMap<String,String>)digestNamesField.get(null);
digestNames.put("1.2.156.10197.1.401", "SM3");
Field algorithmNamesField = EncryptionAlgorithms.class.getDeclaredField("algorithmNames");
algorithmNamesField.setAccessible(true);
HashMap<String, String> algorithmNames = (HashMap<String,String>)algorithmNamesField.get(null);
algorithmNames.put("1.2.156.10197.1.501", "SM2");
String file = "C:/Users/Administrator/Documents/sm2pkcs7sig.pdf";
PdfReader reader = new PdfReader(file);
// 创建签名工具
PdfDocument pdfDocument = new PdfDocument(reader);
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
// 获取所有的签名域,域名称列表
List<String> fieldNames = signatureUtil.getSignatureNames();
for (String fieldName : fieldNames) {
// 获取签名值的PKCS#7数据
PdfPKCS7 pkcs7 = signatureUtil.readSignatureData(fieldName, "BC");
// 数字签名验证是否有效
boolean isValidate = pkcs7.verifySignatureIntegrityAndAuthenticity();
System.out.println("Is signature validate: " + isValidate);
}
}
运行结果
Is signature validate: true
确实解决了该问题,更好的做法是继承PdfPKCS7
然后做一些内部修改,不过我准备向itext7 库搞一个 Pull req 直接在对应的Map中加入算法标识符就行了。