SM2_0">6.开源非对称加密算法SM2实现
前期内容导读:
1. 开源组件 非对称秘钥加密介绍
- 加密组件引入方法:
<dependency> <groupId>com.biuqu</groupId> <artifactId>bq-encryptor</artifactId> <version>1.0.1</version> </dependency>
SM2_17">1.1 SM2的加解密实现
- 加解密核心逻辑
java">public byte[] doCipher(byte[] data, byte[] key, int cipherMode) { SM2Engine.Mode mode = SM2Engine.Mode.C1C2C3; if (!this.getPaddingMode().equalsIgnoreCase(String.valueOf(DEFAULT_MODE))) { mode = SM2Engine.Mode.C1C3C2; } SM2Engine sm2Engine = new SM2Engine(mode); this.initSm2Engine(sm2Engine, key, cipherMode); try { return sm2Engine.processBlock(data, 0, data.length); } catch (Exception e) { throw new EncryptionException("failed to do sm2 cipher.", e); } } private void initSm2Engine(SM2Engine sm2Engine, byte[] key, int cipherMode) { if (Cipher.ENCRYPT_MODE == cipherMode) { ECPublicKey keyObj = (ECPublicKey)this.toPubKey(key); ECDomainParameters domainParam = this.getDomainParam(keyObj); ECKeyParameters keyParam = new ECPublicKeyParameters(keyObj.getQ(), domainParam); byte[] initKey = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8); sm2Engine.init(true, new ParametersWithRandom(keyParam, this.createRandom(initKey))); } else { ECPrivateKey keyObj = (ECPrivateKey)this.toPriKey(key); ECDomainParameters domainParam = this.getDomainParam(keyObj); ECKeyParameters keyParam = new ECPrivateKeyParameters(keyObj.getD(), domainParam); sm2Engine.init(false, keyParam); } }
说明:
SM2_66">1.2 SM2生成秘钥及转换实现
-
秘钥生成逻辑
java">public KeyPair createKey(byte[] initKey) { try { ECGenParameterSpec paramSpec = new ECGenParameterSpec(SM2_VERSION); KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, this.getProvider()); if (null == initKey) { initKey = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8); } SecureRandom random = this.createRandom(initKey); keyGen.initialize(paramSpec, random); return keyGen.generateKeyPair(); } catch (Exception e) { throw new EncryptionException("failed to get sm2 key.", e); } }
-
公钥、私钥反向生成逻辑
java">public PublicKey toPubKey(byte[] pubKey) { try { String hexKey = Hex.toHexString(pubKey); KeyFactory kf = KeyFactory.getInstance(ALGORITHM, this.getProvider()); if (hexKey.startsWith(STANDARD_HEX_KEY_PREFIX)) { return kf.generatePublic(new X509EncodedKeySpec(pubKey)); } else { // 获取SM2相关参数 X9ECParameters ecParam = GMNamedCurves.getByName(SM2_VERSION); // 将公钥HEX字符串转换为椭圆曲线对应的点 ECCurve ecCurve = ecParam.getCurve(); ECPoint ecPoint = ecCurve.decodePoint(pubKey); // 椭圆曲线参数规格 ECParameterSpec ecSpec = new ECParameterSpec(ecCurve, ecParam.getG(), ecParam.getN(), ecParam.getH()); // 将椭圆曲线点转为公钥KEY对象 return kf.generatePublic(new ECPublicKeySpec(ecPoint, ecSpec)); } } catch (Exception e) { throw new EncryptionException("failed to get sm2 pub key.", e); } } public PrivateKey toPriKey(byte[] priKey) { try { String hexKey = Hex.toHexString(priKey); KeyFactory kf = KeyFactory.getInstance(ALGORITHM, this.getProvider()); if (hexKey.startsWith(STANDARD_HEX_KEY_PREFIX)) { PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(priKey); return kf.generatePrivate(keySpec); } else { // 获取SM2相关参数 X9ECParameters ecParam = GMNamedCurves.getByName(SM2_VERSION); ECCurve ecCurve = ecParam.getCurve(); // 椭圆曲线参数规格 ECParameterSpec ecSpec = new ECParameterSpec(ecCurve, ecParam.getG(), ecParam.getN(), ecParam.getH()); // 将私钥HEX字符串转换为16进制的数字值 BigInteger bigInteger = new BigInteger(Hex.toHexString(priKey), EncryptionConst.HEX_UNIT); // 将X值转为私钥KEY对象 return kf.generatePrivate(new ECPrivateKeySpec(bigInteger, ecSpec)); } } catch (Exception e) { throw new EncryptionException("failed to get sm2 pri key.", e); } }
说明:SM2基于椭圆的原理来加解密,其秘钥生成和解析方式也与其它方式不同。
- SM2支持标准的秘钥生成方式:
java">BaseSingleSignature sm2 = new Sm2Encryption(); KeyPair keyPair = sm2.createKey(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); byte[] priKey0 = keyPair.getPrivate().getEncoded(); byte[] pubKey0 = keyPair.getPublic().getEncoded();
- SM2支持非标准的秘钥生成方式:
java">BaseSingleSignature sm2 = new Sm2Encryption(); KeyPair keyPair = sm2.createKey(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); byte[] priKey1 = ((BCECPrivateKey)keyPair.getPrivate()).getD().toByteArray(); byte[] pubKey1 = ((BCECPublicKey)keyPair.getPublic()).getQ().getEncoded(false); byte[] pubKey2 = ((BCECPublicKey)keyPair.getPublic()).getQ().getEncoded(true);
- 上述正文部分的秘钥转换逻辑可以无感兼容上述各种秘钥场景。有兴趣可以看看此算法的单元测试类。PS:网上资料通常只描述了其中一种秘钥生成场景,但是相互间是不兼容的。
-
签名和验签判定逻辑:
java">public byte[] sign(byte[] data, byte[] key) { try { PrivateKey priKey = this.toPriKey(key); Signature signature = Signature.getInstance(this.getSignatureAlg(), this.getProvider()); signature.initSign(priKey); signature.update(data); return signature.sign(); } catch (Exception e) { throw new EncryptionException("failed to signature.", e); } } public boolean verify(byte[] data, byte[] key, byte[] sign) { try { PublicKey pubKey = this.toPubKey(key); Signature signature = Signature.getInstance(this.getSignatureAlg(), this.getProvider()); signature.initVerify(pubKey); signature.update(data); return signature.verify(sign); } catch (Exception e) { throw new EncryptionException("failed to verify signature.", e); } }
-
java">@Test public void encrypt() { int[] encLengths = {256}; super.encrypt(encLengths); } @Test public void testEncryptAndSign() { String initKey = UUID.randomUUID() + new String(RandomUtils.nextBytes(5000), StandardCharsets.UTF_8); int[] encLengths = {256}; BaseSingleSignature encryption = new Sm2Encryption(); for (int encLen : encLengths) { encryption.setEncryptLen(encLen); KeyPair keyPair = encryption.createKey(initKey.getBytes(StandardCharsets.UTF_8)); super.testEncryptAndSign(encryption, keyPair.getPrivate().getEncoded(), keyPair.getPublic().getEncoded()); } }
说明: