基于IText5的SM2算法电子签名

    前言:近做无纸化项目,需要用到电子签名,而网上基本上只有RSA算法的文章,所以看了下itext签名的部分源码,修改出来的sm2算法签名,分为两种,分离签名和后台私钥签名,这里放出来的是后台私钥签名


1.签名过程

获取PDF文档的字节数组----->创建签名域(大小,外观显示等等)------>读取byteRange将文档的字节数组切割成3部分,按顺序命名为buffer1,buffer2,buffer3-->System.arraycopy把buffer1和buffer3相加获取签名原文orgin(按照Adobe官方的说法是buffer1+buffer3但是实际操作中发现buffer1占了绝大部分的加密主体,buffer3只有很小一部分,而且一些小操作比如添加文档属性等会导致buffer3的变动,所以本文实际加密的是buffer1而非buffer1+buffer3)--->将orgin转码发送spi的加解密接口中,获得加密值signValue--->将signValue写入文档中,也就是Contents中--->获取新的签名后文档


2.改造方法

    首先我们仿照RSA的签名过程

  修改如下:

                //调用方法前加入国密的oid,这里用到反射

        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 allowedDigests = DigestAlgorithms.class.getDeclaredField("allowedDigests");
	allowedDigests.setAccessible(true);
	HashMap<String, String> allowedDigestsNames = (HashMap<String, String>) allowedDigests.get(null);
	allowedDigestsNames.put("SM3", "1.2.156.10197.1.401");

	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");
	//SM3的摘要算法
	ExternalDigest digest = new ExternalDigest() {
		@Override
		public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException {
		      return MessageDigest.getInstance("SM3", BC);
		}
	};
	
	
	//私钥签名,实现ExternalSignature 接口 把官方 PrivateKeySignature的内容全部复制过来,重点在构造方法加入SM2和SM3
	public Sm2PrivateKeySignature(PrivateKey pk, String provider) {
		this.pk = pk;
		this.provider = provider;
		this.hashAlgorithm = DigestAlgorithms.getDigest(DigestAlgorithms.getAllowedDigests("SM3"));
		encryptionAlgorithm = "SM2";
	}
	
	//修改 Itext的 PdfPKCS7 类修改 setExternalDigest方法,加入 sm2 sm3的 oid
	public void setExternalDigest(byte digest[], byte RSAdata[], String digestEncryptionAlgorithm) {
		externalDigest = digest;
		externalRSAdata = RSAdata;
		if (digestEncryptionAlgorithm != null) {
			if (digestEncryptionAlgorithm.equals("RSA")) {
				this.digestEncryptionAlgorithmOid = SecurityIDs.ID_RSA;
			} else if (digestEncryptionAlgorithm.equals("DSA")) {
				this.digestEncryptionAlgorithmOid = SecurityIDs.ID_DSA;
			} else if (digestEncryptionAlgorithm.equals("ECDSA")) {
				this.digestEncryptionAlgorithmOid = SecurityIDs.ID_ECDSA;
			} else if (digestEncryptionAlgorithm.equals("SM2")) { //sm2
				this.digestEncryptionAlgorithmOid = GMObjectIdentifiers.sm2sign_with_sm3.getId();
			} else if (digestEncryptionAlgorithm.equals("SM3")) { //sm3
				this.digestEncryptionAlgorithmOid = GMObjectIdentifiers.sm3.getId();
			} else
				throw new ExceptionConverter(new NoSuchAlgorithmException(
						MessageLocalization.getComposedMessage("unknown.key.algorithm.1", digestEncryptionAlgorithm)));
		}
	}	
	
	


        //把MakeSignature类里signDetached签名方法的 setExternalDigest替换为上面修改过的 ,这里最好重写一遍

            微信截图_20201203140459.png



提供完整的签名例子和sm2时间戳,sm2测试p12证书

demo地址:https://gitee.com/caoxusheng/itext5-sm2/

sm2时间戳地址:http://47.98.124.142:9091/service/tsa?type=sm2


测试证书颁发地址:可颁发 RSA算法和SM2算法 http://47.98.124.142:9091/cert/applyCert

由于sm2签名在adobe pdf上识别不出来会显示无效,这里可以使用CFCA的验签平台 https://verify.cfca.com.cn/VerifySignWeb/frame.do

验签结果:

微信截图_20201203150418.png 

评论区

大漠过客

2020-12-10 09:19

很有用,谢谢分享!另外能否分享一下测试证书颁发(http://47.98.124.142:9091/cert/applyCert)的代码?

caoxusheng

2020-12-10 11:18

@大漠过客 要过段时间,会开源一个我开发的小型CA系统

大漠过客

2020-12-11 12:43

@caoxusheng 太棒了,非常期待!

laudavy

2020-12-20 19:08

@caoxusheng 非常期待.