项目数据接口国密支持
说明
国密即国家密码局认定的国产密码算法,即商用密码。
国密主要有SM1,SM2, SM3, SM4。密钥长度和分组长度均为128位。
1、SM1为对称加密。其加密强度与AES(高级加密标准, Advanced Encryption Standard)相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
2、SM2为非对称加密,基于ECC。 该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。
3、SM3为消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。
4、SM4为无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。
由于SM1、SM4加解密的分组大小为128bit,故对消息进行加解密时,若消息长度过长,需要进行分组,要消息长度不足,则要进行填充。
国家在大力推进商用密码应用,并大力推广国产化
尤其在甲方为国企客户情况,需要针对产品上行到平台的数据以及对平台下行到设备的数据进行国密加解密!!!
项目使用
对SM2/SM4支持
项目为spingboot的Java常规maven管理的,jdk1.8+;
添加Maven依赖
<!--SM2加密算法依赖-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<!-- SM4加密算法依赖-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId >hutool-crypto</artifactId>
<version>5.7.1</version>
</dependency>
编写工具类
@Component
public class EncryptionUtils {
// SM2非对称加密
public static String sm2Encrypt(String publicKey,String plainText) {
SM2 sm2 = new SM2(null, publicKey);
String res = sm2.encryptBcd(plainText, KeyType.PublicKey);
return res;
}
// SM2非对称解密
public static String sm2Decrypt(String privatekey, String cipherText) {
SM2 sm2 = new SM2(privatekey, null);
return sm2.decryptStr(cipherText, KeyType.PrivateKey ,CharsetUtil.CHARSET_UTF_8);
}
// SM4对称加密
public static String sm4Encrypt(String secretKey, String plainText) {
SM4 sm4 = SmUtil.sm4(secretKey.getBytes());
return sm4.encryptHex(plainText);
}
// SM4对称解密
public static String sm4Decrypt(String secretKey, String cipherText) {
SM4 sm4 = SmUtil.sm4(secretKey.getBytes());
return sm4.decryptStr(cipherText, CharsetUtil.CHARSET_UTF_8);
}
public static void main(String[] args) {
// 生成SM2秘钥
// Map<String, String> smKeyMap = EncryptionUtils.generateSm2Key();
// String publicKey = smKeyMap.get("KEY_PUBLIC_KEY");
// String privateKey = smKeyMap.get("KEY_PRIVATE_KEY");
// System.out.println("公钥:" + publicKey);
// System.out.println("私钥:" + privateKey);
// 生成SM4秘钥
System.out.println("生成SM4秘钥:"+EncryptionUtils.generateKey());
}
/**
* 生成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;
}
public static String generateKey() {
// 生成SM4秘钥
String key = RandomUtil.randomString(16);
System.out.println("生成1个128bit的加密key:"+key);
return key;
}
}
项目切面编程
需要解密则需要使用拦截器等进行请求前置处理
定义注解:
/**
* 加密生成类 注解,对返回数据进行加密
* 响应结果加密
* 条件:
* 1.添加注解:@EncryptionAnnotation
* 2.返回类型为 R / AjaxResult
* 加密内容为:R.data -> R.encryptData
* 加密算法支持:国密-SM2/SM4
* @author xiaohang
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptionAnnotation {
}
切面编程:
对返回类需要根据项目定义进行统一配置修改
/**
* 响应结果加密
* 条件:
* 1.添加注解:@EncryptionAnnotation
* 2.返回类型为 AjaxResult
* 加密内容为:AjaxResult.data -> R.encryptData
* 加密算法支持:国密-SM2/SM4
* @author xiaohang
*/
@ControllerAdvice
public class EncryptAjaxResultResponseBodyAdvice implements ResponseBodyAdvice<AjaxResult> {
@Value("${data.encrypt.publicKey}")
private String publicKey;
@Value("${data.encrypt.privateKey}")
private String privatekey;
@Value("${data.encrypt.open}")
private boolean open;
@Value("${data.encrypt.showLog}")
private boolean showLog;
@Value("${data.encrypt.type}")
private String encryptType;
@Value("${data.encrypt.secretKey}")
private String secretKey;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (!returnType.hasMethodAnnotation(EncryptionAnnotation.class)){
return false;
}
String returnName = returnType.getParameterType().getName();
if (returnName.equals(AjaxResult.class.getName()) ) {
return true;
}
return false;
}
@SneakyThrows
@Override
public AjaxResult beforeBodyWrite(AjaxResult body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (!open) {
return body;
}
// 真实数据
Object data = body.get(AjaxResult.DATA_TAG);
// 如果data为空,直接返回
if (data == null) {
return body;
}
String dataText = JSONUtil.toJsonStr(data);
// 如果data为空,直接返回
if (StringUtils.isBlank(dataText)) {
return body;
}
String encryptText;
try {
if ("SM2".equals(encryptType)){
encryptText = EncryptionUtils.sm2Encrypt(publicKey,dataText);
}else if ("SM4".equals(encryptType)){
encryptText = EncryptionUtils.sm4Encrypt(secretKey,dataText);
}else{
encryptText = null;
}
if (showLog){
System.out.println("加密前明文:"+dataText);
System.out.println("加密后密文:"+encryptText);
}
}catch (Exception e){
e.printStackTrace();
encryptText="数据加密异常,请联系管理人员!";
}
body.put(AjaxResult.ENCRYPT_DATA,encryptText);
body.put(AjaxResult.DATA_TAG,null);
return body;
}
}
参数配置
# 数据加密
data:
encrypt:
open: true
showLog: false
type: SM2
publicKey: 【SM2的公钥】
privateKey: 【SM2的私钥】
secretKey: 【SM4的密钥】
测试使用
@Slf4j
@RestController
@RequestMapping("test")
public class TestController extends BaseController{
@GetMapping("encryption")
@EncryptionAnnotation
public AjaxResult encryption2(String data){
CommercialTenant t = new CommercialTenant();
t.setBrandName("加密测试,返回AjaxResult的数据");
return AjaxResult.success(t);
}
}