JAVA集成国密SM3加密、验签
- 一、pom配置
- 二、加密代码集成
- 2.1、目录结构
- 2.2、源码
- 2.3、测试
- 三、验签代码集成
- 2.1、目录结构
- 2.2、源码
- 2.3、测试
- 四、相关链接
国密算法概述:https://blog.csdn.net/qq_38254635/article/details/131801527
SM3杂凑算法
SM3 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。
一、pom配置
<!-- 国密 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.66</version>
</dependency>
<!-- 验签 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.5</version>
</dependency>
二、加密代码集成
2.1、目录结构
2.2、源码
SecretCommon.java
package com.secret.sm3;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
/**
* SM3密码杂凑算法(哈希算法)
* SM3杂凑算法是我国自主设计的密码杂凑算法。
* 适用于商用密码应用中的数字签名和验证消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。
* 为了保证杂凑算法的安全性,其产生的杂凑值的长度不应太短。
* 例如MD5输出128比特杂凑值,输出长度太短,影响其安全性SHA-1算法的输出长度为160比特,SM3算法的输出长度为256比特,因此SM3算法的安全性要高于MD5算法和SHA-1算法。
*/
public class SecretCommon {
/**
* sm3算法加密,不可逆加密
* @param plainText 需加密的明文字符串
* @return 加密后固定长度64的16进制字符串
*/
public static String encrypt(String plainText) {
return ByteUtils.toHexString(encrypt(plainText.getBytes()));
}
/**
* sm3算法加密,不可逆加密
* @param plainByte 需加密的明文数组
* @return 加密后固定长度64的16进制数组
*/
public static byte[] encrypt(byte[] plainByte) {
SM3Digest sm3Digest = new SM3Digest();
sm3Digest.update(plainByte, 0, plainByte.length);
byte[] digestByte = new byte[sm3Digest.getDigestSize()];
sm3Digest.doFinal(digestByte, 0);
return digestByte;
}
/**
* sm3算法通过密钥进行加密,不可逆加密
* @param keyText 密钥字符串
* @param plainText 需加密的明文字符串
* @return 加密后固定长度64的16进制字符串
*/
public static String encryptByKey(String keyText, String plainText) {
return ByteUtils.toHexString(encryptByKey(keyText.getBytes(), plainText.getBytes()));
}
/**
* sm3算法通过密钥进行加密,不可逆加密
* @param keyByte 密钥数组
* @param plainByte 需加密的明文数组
* @return 加密后固定长度64的16进制数组
*/
public static byte[] encryptByKey(byte[] keyByte, byte[] plainByte) {
KeyParameter keyParameter = new KeyParameter(keyByte);
SM3Digest sm3Digest = new SM3Digest();
HMac hMac = new HMac(sm3Digest);
hMac.init(keyParameter);
hMac.update(plainByte, 0, plainByte.length);
byte[] result = new byte[hMac.getMacSize()];
hMac.doFinal(result, 0);
return result;
}
}
Utils.java
package com.secret.sm3;
public class Utils {
/**
* sm3算法加密,不可逆加密
* @param plainText 需加密的明文字符串
* @return 加密后固定长度64的16进制字符串
*/
public static String encrypt(String plainText) {
return SecretCommon.encrypt(plainText);
}
/**
* sm3算法通过密钥进行加密,不可逆加密
* @param keyByte 密钥字符串
* @param plainText 需加密的明文字符串
* @return 加密后固定长度64的16进制字符串
*/
public static String encryptByKey(String keyText, String plainText) {
return SecretCommon.encryptByKey(keyText, plainText);
}
}
测试类:Test.java
package com.secret.sm3;
public class Test {
public static void main(String[] args) {
System.out.println("SM3加密:" + Utils.encrypt("I believe you can do anything"));
System.out.println("SM3秘钥加密:" + Utils.encryptByKey("myKey", "Do what you want to do"));
}
}
2.3、测试
使用方法参考测试类即可。
三、验签代码集成
2.1、目录结构
2.2、源码
SignatureCommon.java
package com.secret.sm3.signature;
import cn.hutool.crypto.SmUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SignatureCommon {
private static Logger log = LoggerFactory.getLogger(SignatureCommon.class);
/**
* 验签(JSON方式,较简单)
* @param sign 前端传入签名(可从请求报文头获取)
* @param object 传输实体模型
* @param privateKey 签名加密私钥
* @param timestamp 时间戳(可从请求报文头获取)
*/
public static boolean checkSignJson(String sign, Object object, String privateKey, Long timestamp) {
return checkSignJson(sign, object, privateKey, timestamp, SignatureConstant.SIGNATURE_VALIDITY_TIME);
}
/**
* 验签(JSON方式,较简单)
* @param sign 前端传入签名(可从请求报文头获取)
* @param object 传输实体模型
* @param privateKey 签名加密私钥
* @param timestamp 时间戳(可从请求报文头获取)
* @param validityTime 校验时间,单位分钟
*/
public static boolean checkSignJson(String sign, Object object, String privateKey, Long timestamp, Integer validityTime) {
//关闭验签,默认TRUE
if(!SignatureConstant.SIGNATURE_SWITCH) return true;
if(!checkTime(timestamp, validityTime)) return false;
String localSign = getSign(new SignatureStrategyJson().getSignValue(object, privateKey, timestamp));
return localSign.toUpperCase().equals(sign.toUpperCase());
}
/**
* 验签(JSON方式,较简单)
* @param sign 前端传入签名(可从请求报文头获取)
* @param object 传输实体模型
* @param privateKey 签名加密私钥
* @param timestamp 时间戳(可从请求报文头获取)
*/
public static boolean checkSignArray(String sign, Object object, String privateKey, Long timestamp) {
return checkSignArray(sign, object, privateKey, timestamp, SignatureConstant.SIGNATURE_VALIDITY_TIME);
}
/**
* 验签(JSON方式,较简单)
* @param sign 前端传入签名(可从请求报文头获取)
* @param object 传输实体模型
* @param privateKey 签名加密私钥
* @param timestamp 时间戳(可从请求报文头获取)
* @param validityTime 校验时间,单位分钟
*/
public static boolean checkSignArray(String sign, Object object, String privateKey, Long timestamp, Integer validityTime) {
//关闭验签,默认TRUE
if(!SignatureConstant.SIGNATURE_SWITCH) return true;
if(!checkTime(timestamp, validityTime)) return false;
String localSign = getSign(new SignatureStrategyArray().getSignValue(object, privateKey, timestamp));
return localSign.toUpperCase().equals(sign.toUpperCase());
}
/**
* 加密签名
* @param signValue 待加密签名字符串
* @return 加密后签名字符串
*/
public static String getSign(String signValue){
return SmUtil.sm3(signValue);
}
/**
* 校验时间范围
* @param timestamp 页面时间
*/
public static boolean checkTime(Long timestamp, Integer validityTime) {
Long nowTime = System.currentTimeMillis();
Long difference = nowTime - timestamp;
//配置时间大于0,则验证时间戳,在时间范围内才处理
if (validityTime > 0){
if(difference >= 60000 * validityTime || difference <= 0){
log.error("时间戳异常,非" + validityTime + "分钟内请求,当前时间戳:" + nowTime);
return false;
}
}
return true;
}
}
SignatureConstant.java
package com.secret.sm3.signature;
public class SignatureConstant {
public static final Boolean SIGNATURE_SWITCH = true; //请求签名开关
@Deprecated
public static final Boolean SIGNATURE_SWITCH_TRUE = true; //请求签名开
@Deprecated
public static final Boolean SIGNATURE_SWITCH_FALSE = false; //请求签名关
public static final Integer SIGNATURE_VALIDITY_TIME = 1; //请求签名有效时间 默认1分钟
}
SignatureStrategy.java
package com.secret.sm3.signature;
public interface SignatureStrategy {
String getSignValue(Object object);
String getSignValue(Object object, String privateKey, Long timestamp);
}
SignatureStrategyArray.java
package com.secret.sm3.signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
public class SignatureStrategyArray implements SignatureStrategy {
private static Logger log = LoggerFactory.getLogger(SignatureStrategyArray.class);
@Override
public String getSignValue(Object object, String privateKey, Long timestamp) {
return getSignValue(object) + "&privateKey=" + privateKey + "×tamp=" + timestamp;
}
/**
* 获取实体类拼成的加密字段
* @param object 参数实体类
* @return 待加密字符串
*/
@Override
public String getSignValue(Object object) {
Field[] fields = object.getClass().getDeclaredFields();//获取所有属性
String[][] fieldArray = new String[fields.length][2]; //用二维数组保存 参数名和参数值
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
fieldArray[i][0] = fields[i].getName().toLowerCase(); //获取属性名
try {
fieldArray[i][1] = String.valueOf(fields[i].get(object)); //把属性值放进数组
} catch (IllegalAccessException e) {
log.error("signature field:" + fields[i].getName() + "Failed to add value");
}
}
fieldArray = doChooseSort(fieldArray); //对参数实体类按照字母顺序排续
StringBuilder result = new StringBuilder();
for (String[] field : fieldArray) {//按照签名规则生成待加密字符串
result.append(field[0]).append("=").append(field[1]).append("&");
}
result = new StringBuilder(result.substring(0, result.length() - 1));//消除掉最后的“&”
log.info("signature Information:" , result.toString());
return result.toString();
}
/**
* 对二维数组里面的数据进行选择排序,按字段名按abcd顺序排列
* @param data 未按照字母顺序排序的二维数组
*/
private String[][] doChooseSort(String[][] data) {//排序方式为选择排序
int n = data.length;
for (int i = 0; i < n - 1; i++) {
int k = i; // 初始化最小值的小标
for (int j = i + 1; j < n; j++) {
if (data[k][0].compareTo(data[j][0]) > 0) { //下标k字段名大于当前字段名
k = j;// 修改最大值的小标
}
}
// 将最小值放到排序序列末尾
if (k > i) { //用相加相减法交换data[i] 和 data[k]
String tempValue ;
tempValue = data[k][0];
data[k][0] = data[i][0];
data[i][0] = tempValue;
tempValue = data[k][1];
data[k][1] = data[i][1];
data[i][1] = tempValue;
}
}
return data;
}
}
SignatureStrategyJson.java
package com.secret.sm3.signature;
import cn.hutool.json.JSONUtil;
public class SignatureStrategyJson implements SignatureStrategy {
@Override
public String getSignValue(Object object, String privateKey, Long timestamp) {
return getSignValue(object) + "&privateKey=" + privateKey + "×tamp=" + timestamp;
}
/**
* 获取实体类拼成的加密字段
* @param object 传入参数实体类
* @return 待加密字符串
*/
public String getSignValue(Object object) {
return JSONUtil.toJsonStr(object);
}
}
Utils.java
package com.secret.sm3.signature;
public class Utils {
/**
* 验签(JSON方式,较简单)
* @param sign 前端传入签名(可从请求报文头获取)
* @param object 传输实体模型
* @param privateKey 签名加密私钥
* @param timestamp 时间戳(可从请求报文头获取)
*/
public static boolean checkSign(String sign, Object object, String privateKey, Long timestamp) {
return SignatureCommon.checkSignJson(sign, object, privateKey, timestamp);
}
/**
* 验签(JSON方式,较简单)
* @param sign 前端传入签名(可从请求报文头获取)
* @param object 传输实体模型
* @param privateKey 签名加密私钥
* @param timestamp 时间戳(可从请求报文头获取)
* @param validityTime 校验时间,单位分钟
*/
public static boolean checkSign(String sign, Object object, String privateKey, Long timestamp, Integer validityTime) {
return SignatureCommon.checkSignJson(sign, object, privateKey, timestamp, validityTime);
}
/**
* 获取签名(JSON方式,较简单)
* @param object 传输实体模型
* @param privateKey 签名加密私钥
* @param timestamp 时间戳
*/
public static String getSignValue(Object object, String privateKey, Long timestamp) {
return SignatureCommon.getSign(new SignatureStrategyJson().getSignValue(object, privateKey, timestamp));
}
}
测试类:TestBean.java
package com.secret.sm3.signature;
public class TestBean {
private String name;
private String idCard;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public TestBean() {
}
public TestBean(String name, String idCard) {
this.name = name;
this.idCard = idCard;
}
}
测试类:Test.java
package com.secret.sm3.signature;
public class Test {
public static void main(String[] args) throws Exception {
//模拟请求
TestBean bean = new TestBean("张三", "532143200009129565");
Long nowTime = System.currentTimeMillis();
String privateKey = "143s57a7b60841c2";
String errorSign = "Today is really a beautiful day";
System.out.println("一.错误签名示例,验签结果:" + Utils.checkSign(errorSign, bean, privateKey, nowTime));
//2.签名、时间均正确的示例
String rightSign = Utils.getSignValue(bean, privateKey, nowTime);
System.out.println("二.正确签名示例,验签结果:" + Utils.checkSign(rightSign, bean, privateKey, nowTime));
//3.签名正确,时间错误的示例
Long oldTime = 1672502400000L; //示例时间:2023-01-01 00:00:00
System.out.println("三.错误时间示例,验签结果:" + Utils.checkSign(rightSign, bean, privateKey, oldTime));
}
}
2.3、测试
使用方法参考测试类即可。
四、相关链接
国密算法概述:https://blog.csdn.net/qq_38254635/article/details/131801527
JAVA集成国密SM2:https://blog.csdn.net/qq_38254635/article/details/131810661
JAVA集成国密SM4:https://blog.csdn.net/qq_38254635/article/details/131810715