赞
踩
使用国密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);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。