使用国密SM2和SM3引入Maven
<!-- hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.2</version>
</dependency>
<!-- 国密Jar -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.62</version>
<scope>compile</scope>
</dependency>
创建SM3的工具类
import cn.hutool.crypto.SmUtil;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Objects;
@Component
public class Sm3UtilHua {
private static Integer httpCheckSignTimeOut = 1;//请求签名有效时间 默认1分钟
/**
* SM3加密方式之:不提供密钥的方式 SM3加密,返回加密后长度为64位的16进制字符串
*
* @param src 明文
* @return
*/
public static String encrypt(String src) {
return ByteUtils.toHexString(getEncryptBySrcByte(src.getBytes()));
}
/**
* 返回长度为32位的加密后的byte数组
*
* @param srcByte
* @return
*/
public static byte[] getEncryptBySrcByte(byte[] srcByte) {
SM3Digest sm3 = new SM3Digest();
sm3.update(srcByte, 0, srcByte.length);
byte[] encryptByte = new byte[sm3.getDigestSize()];
sm3.doFinal(encryptByte, 0);
return encryptByte;
}
/**
* 获取实体类拼成的加密字段
*
* @param checkSign 前端传入的签名(从请求报文头获取)
* @param signModel 查询的DTO模型类
* @param privateKey 签名加密私钥
* @param timestamp 时间戳(从请求报文头获取)
* @return 比对结果
*/
public boolean checkSign(String checkSign, Object signModel, String privateKey, Long timestamp) throws Exception {
Long thisTime = System.currentTimeMillis() - timestamp;
Integer checkSignTimeOut = httpCheckSignTimeOut;
if (!(Objects.isNull(checkSignTimeOut) || checkSignTimeOut.intValue() == 0)) {
//时间为0或者未配置签名超时时间,默认不验证时间戳
if (thisTime >= 60 * 1000 * checkSignTimeOut || thisTime <= 0) {
//checkSignTimeOut分钟内的时间戳才处理
System.out.println("时间戳异常,非" + checkSignTimeOut + "分钟内请求,当前时间戳:" + System.currentTimeMillis());
return false;
}
}
String signValue = getSignValue(signModel) + "×tamp=" + timestamp + "&privateKey=" + privateKey;
String sign = getSign(signValue);
System.out.println("【本地加密后 sm3 签名】" + sign);//生产上建议注释此行,防止泄露
return sign.toUpperCase().equals(checkSign.toUpperCase()) ? true : false;
}
/**
* 加密签名
*
* @param signValue 待加密签名字符串
* @return 加密后签名字符串
*/
public String getSign(String signValue) {
return SmUtil.sm3(signValue);
}
/**
* 获取实体类拼成的加密字段
*
* @param classA 传入参数实体类
* @return 待加密字符串
*/
public String getSignValue(Object classA) {
Field[] fs = classA.getClass().getDeclaredFields();//获取所有属性
String[][] temp = new String[fs.length][2]; //用二维数组保存 参数名和参数值
for (int i = 0; i < fs.length; i++) {
fs[i].setAccessible(true);
temp[i][0] = fs[i].getName().toLowerCase(); //获取属性名
try {
temp[i][1] = String.valueOf(fs[i].get(classA));//把属性值放进数组
} catch (Exception e) {
System.out.println("【签名字段:" + fs[i].getName() + "添加失败】");
}
}
temp = doChooseSort(temp); //对参数实体类按照字母顺序排续
String result = "";
for (int i = 0; i < temp.length; i++) {//按照签名规则生成待加密字符串
result = result + temp[i][0] + "=" + temp[i][1] + "&";
}
result = result.substring(0, result.length() - 1);//消除掉最后的“&”
System.out.println("【签名信息】{}" + result);
return result;
}
/**
* 对二维数组里面的数据进行选择排序,按字段名按abcd顺序排列
*
* @param data 未按照字母顺序排序的二维数组
* @return
*/
private String[][] doChooseSort(String[][] data) {//排序方式为选择排序
String[][] temp = new String[data.length][2];
temp = data;
int n = temp.length;
for (int i = 0; i < n - 1; i++) {
int k = i;// 初始化最小值的小标
for (int j = i + 1; j < n; j++) {
if (temp[k][0].compareTo(temp[j][0]) > 0) { //下标k字段名大于当前字段名
k = j;// 修改最大值的小标
}
}
// 将最小值放到排序序列末尾
if (k > i) { //用相加相减法交换data[i] 和 data[k]
String tempValue;
tempValue = temp[k][0];
temp[k][0] = temp[i][0];
temp[i][0] = tempValue;
tempValue = temp[k][1];
temp[k][1] = temp[i][1];
temp[i][1] = tempValue;
}
}
return temp;
}
}
创建SM2工具类
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.jce.interfaces.ECPrivateKey;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import java.util.HashMap;
import java.util.Map;
public class Sm2Util {
/**
* 公钥常量
*/
public static final String KEY_PUBLIC_KEY = "publicKey";
/**
* 私钥返回值常量
*/
public static final String KEY_PRIVATE_KEY = "privateKey";
/**
* 生成SM2公私钥
*
* @return
*/
public static Map<String, String> generateSm2Key() {
SM2 sm2 = new SM2();
ECPublicKey publicKey = (ECPublicKey) sm2.getPublicKey();
ECPrivateKey privateKey = (ECPrivateKey) sm2.getPrivateKey();
// 获取公钥
byte[] publicKeyBytes = publicKey.getQ().getEncoded(false);
String publicKeyHex = HexUtil.encodeHexStr(publicKeyBytes);
// 获取64位私钥
String privateKeyHex = privateKey.getD().toString(16);
// BigInteger转成16进制时,不一定长度为64,如果私钥长度小于64,则在前方补0
StringBuilder privateKey64 = new StringBuilder(privateKeyHex);
while (privateKey64.length() < 64) {
privateKey64.insert(0, "0");
}
Map<String, String> result = new HashMap<>();
result.put(KEY_PUBLIC_KEY, publicKeyHex);
result.put(KEY_PRIVATE_KEY, privateKey64.toString());
return result;
}
/**
* SM2私钥签名
*
* @param privateKey 私钥
* @param content 待签名内容
* @return 签名值
*/
public static String sign(String privateKey, String content) {
SM2 sm2 = new SM2(privateKey, null);
return sm2.signHex(HexUtil.encodeHexStr(content));
}
/**
* SM2公钥验签
*
* @param publicKey 公钥
* @param content 原始内容
* @param sign 签名
* @return 验签结果
*/
public static boolean verify(String publicKey, String content, String sign) {
SM2 sm2 = new SM2(null, publicKey);
return sm2.verifyHex(HexUtil.encodeHexStr(content), sign);
}
/**
* SM2公钥加密
*
* @param content 原文
* @param publicKey SM2公钥
* @return
*/
public static String encryptBase64(String content, String publicKey) {
SM2 sm2 = new SM2(null, publicKey);
return sm2.encryptBase64(content, KeyType.PublicKey);
}
/**
* SM2私钥解密
*
* @param encryptStr SM2加密字符串
* @param privateKey SM2私钥
* @return
*/
public static String decryptBase64(String encryptStr, String privateKey) {
SM2 sm2 = new SM2(privateKey, null);
return StrUtil.utf8Str(sm2.decrypt(encryptStr, KeyType.PrivateKey));
}
}
首先开始生成SM2的公钥密钥
public static Map<String, String> initSM2Key() {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //设置权限 否则会报错
SM2 sm2 = SmUtil.sm2();
//这里会自动生成对应的随机秘钥对 , 注意! 这里一定要强转,才能得到对应有效的秘钥信息
byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
//这里公钥不压缩 公钥的第一个字节用于表示是否压缩 可以不要
byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
//打印当前的公私秘钥
System.out.println("私钥: " + HexUtil.encodeHexStr(privateKey));
System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey));
Map<String, String> map = Maps.newHashMap();
map.put(OsConstants.PUBLIC_KEY, HexUtil.encodeHexStr(publicKey));
map.put(OsConstants.PRIVATE_KEY, HexUtil.encodeHexStr(privateKey));
return map;
}
开始写自己的业务逻辑
// 创建参数(自己根据业务定义)
License license = new License();
license.setVersion("1.0");
license.setProduct("测试产品");
license.setOrganization("A公司");
license.setSignaturer(""); // 签名
// 生成公钥密钥 自己拿密钥 给对方公钥
Map<String, String> map = initSM2Key();
String publicKey = map.get(OsConstants.PUBLIC_KEY);
String privateKey = map.get(OsConstants.PRIVATE_KEY);
//使用SM3对参数进行加密 后期不需要反解析
String hexStrNoKey = Sm3UtilHua.encrypt(JSON.toJSONString(license));
// 使用私钥对参对加密后的参数进行一个加签 后续对方根据公钥可以进行验签
String sign = Sm2Util.sign(privateKey, hexStrNoKey);
System.out.println("签名:" + sign);
// 下面是验签过程 对方拿到公钥后调用SM2Utils进行验证
String str = Sm3UtilHua.encrypt(JSON.toJSONString(license));
boolean verify = Sm2Util.verify(publicKey, str , sign);
System.out.println("正确的验签结果:" + verify);