欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

PDF itext pkcs#7 国密签名验证

程序员文章站 2022-03-14 20:21:57
...

环境需求

  • 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文件

通过上述方法我们在验证类似于SHA1withRSASHA256withRSA的时候是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中EncryptionAlgorithmsDigestAlgorithms 类的映射关系中没有国密算法的标识符

那么为了简单的解决问题我们直接通过反射加入然后测试是否能够解决

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中加入算法标识符就行了。

参考

[1]. GMSSL . http://gmssl.org/docs/oid.html