SM2加密传输解决方案

后端

引入依赖


<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.8.5</version>
</dependency>

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk15to18</artifactId>
  <version>1.71</version>
</dependency>

密钥对生成

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.rs.crypto.core.KeyPairInfo;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.SecureRandom;

/**
 * 国密sm2密钥对生成工具
 *  
 * @author 吕晓飞
 * @date 2022-08-31 18:07
 * @version 1.0
 */
public class Sm2KeyGenUtil {

	/**
     * 生成SM2算法的密钥对 - hutool
     *
     * @return 密钥对
     */
    public static KeyPairInfo genSM2KeyUseHutool() {
        // sm2密钥生成
        KeyPair pair = SecureUtil.generateKeyPair("SM2");
        SM2 sm2 = new SM2(pair.getPrivate(), pair.getPublic());
        String privatekey = Hex.toHexString(sm2.getD());
        System.out.println("privatekey-->" + privatekey);
        String publickey = Hex.toHexString(sm2.getQ(false));
        System.out.println("publickey-->" + publickey);
        return new KeyPairInfo(publickey, privatekey);
    }

    /**
     * 生成SM2算法的密钥对 -- 自行实现
     *
     * @return 密钥对
     */
    public static KeyPairInfo genSM2Key() {
        try {
            X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");// SM2默认曲线
            ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
            ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
            keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
            AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();

            // 上面的代码都是直接用maven依赖中的包直接import就可以用了
            // 还有一些更底层的写法,可以自己搜索一下,图方便的这个挺好的

            // 私钥,16进制格式,自己保存
            BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
            String privateKeyHex = privatekey.toString(16);
            System.out.println("private Key :" + privateKeyHex);

            // 公钥,16进制格式,Q值发给前端
            ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
            String publicKeyHex = Hex.toHexString(ecPoint.getEncoded(false));
            System.out.println("Public Key :" + publicKeyHex);
            return new KeyPairInfo(publicKeyHex, privateKeyHex);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
import lombok.*;

/**
 * 密钥对
 *
 * @author 吕晓飞
 * @version 1.0
 * @since 2022-08-31 18:04
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class KeyPairInfo {

    /**
     * 公钥, 用来加密
     */
    private String publicKey;

    /**
     * 私钥, 用来解密
     */
    private String privateKey;
}

新建两个txt文件, 改名为public_key.pem和private_key.pem将生成的公钥和私钥粘贴到文件里保存
将这两个文件放到maven工程的src/main/resources/sm2/目录下

import org.bouncycastle.crypto.engines.SM2Engine;

import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;

/**
 * 国密sm2算法<br>
 * 最初无法解密前端传递的数据 详见https://gitee.com/dromara/hutool/issues/I3AEPJ <br>
 * 需要前端加密的数据是否是04开头, 如果不是需要手动加上04<br>
 * 参考 https://blog.csdn.net/ww_1997/article/details/115379258
 * 
 * @author 吕晓飞
 * @date 2022-08-18 15:50
 * @version 1.0
 */
public class SM2Util {

	/**
	 * 公钥
	 */
	private static String puclicKey = null;

	/**
	 * 私钥
	 */
	private static String privateKey = null;

	/**
	 * 国密sm2算法
	 */
	private static SM2 sm2 = null;

	static {
		puclicKey = ResourceUtil.readUtf8Str("sm2/public_key.pem");
		privateKey = ResourceUtil.readUtf8Str("sm2/private_key.pem");
		sm2 = SmUtil.sm2(privateKey, puclicKey);
		sm2.usePlainEncoding();
		// 加密方式
		// https://blog.csdn.net/weixin_44724660/article/details/122319132
		// 1 - C1C3C2,0 - C1C2C3,默认为1, 与前端保持一致, 设置为C1C2C3
		sm2.setMode(SM2Engine.Mode.C1C2C3);
	}

	/**
	 * 加密字符串
	 *
	 * @param dataStr 原始数据字符串
	 * @return java.lang.String 加密后的16进制字符串
	 */
	public static String encode(String dataStr) {
		return sm2.encryptHex(dataStr, KeyType.PublicKey);
	}

	/**
	 * 解密字符串
	 *
	 * @param encrypedHexStr 加密后的16进制字符串
	 * @return java.lang.String 解密后的字符串
	 */
	public static String decode(String encrypedHexStr) {
		if (!encrypedHexStr.startsWith("04")) {
			encrypedHexStr = "04" + encrypedHexStr;
		}
		return sm2.decryptStr(encrypedHexStr, KeyType.PrivateKey);
	}

}

前端

引入依赖

npm install --save sm-crypto
const sm2 = require('sm-crypto').sm2;

// 公钥 - 用来加密 - 由后端生成发送给前端
let publicKey  = "0463462d2611966c08430219d64b2b1166807951bee2f9b41a615f1d20c3e701a5fe2c239454b7646161b6b3e14ee0c8b36d44f6fa4727e8a0b04180ebf3c6a127";
// 私钥 - 用来解密 - 由后端生成发送给前端
let privateKey = "1776f049d4bddea7829a8dcade066d96798cf720fe142e5a1b3b9f7263e8fe16";

// 验证公钥
let verifyResult = sm2.verifyPublicKey(publicKey);
console.log(verifyResult);

// 1-C1C3C2 / 0-C1C2C3,默认为1, 与后端保持一致, 这里我们设为0 -C1C2C3
const cipherMode = 0;

// 要加密的字符串
let str = "admin";

// 加密
let encryptData = sm2.doEncrypt(str, publicKey, cipherMode);
console.log(encryptData);

// 解密
let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode);
console.log(decryptData);

前端一般只需要加密, 所以只需要配置下公钥就行, 私钥出于安全考虑可以不作声明

posted @ 2022-09-22 20:00  iminifly  阅读(1412)  评论(0编辑  收藏  举报