前言

今天给大家带来一个国产SM4加密解密算法的java后端解决方案,代码完整,可以直接使用,希望给大家带来帮助,尤其是做政府系统的开发人员,可以直接应用到项目中进行加密解密。

画重点!是SM4哦,不是SM。哈哈,各位要在知识里遨游,不要想歪。正文开始~

国产SM4加密解密算法概念介绍

SMS4算法是在国内广泛使用的WAPI无线网络标准中使用的加密算法,是一种32轮的迭代非平衡Feistel结构的分组加密算法,其密钥长度和分组长度均为128。SMS4算法的加解密过程中使用的算法是完全相同的,唯一不同点在于该算法的解密密钥是由它的加密密钥进行逆序变换后得到的。

SMS4分组加密算法是中国无线标准中使用的分组加密算法,在2012年已经被国家商用密码管理局确定为国家密码行业标准,标准编号GM/T 0002-2012并且改名为SM4算法,与SM2椭圆曲线公钥密码算法,SM3密码杂凑算法共同作为国家密码的行业标准,在我国密码行业中有着极其重要的位置。

SMS4算法的分组长度为128bit,密钥长度也是128bit。加解密算法均采用32轮非平衡Feistel迭代结构,该结构最先出现在分组密码LOKI的密钥扩展算法中。SMS4通过32轮非线性迭代后加上一个反序变换,这样只需要解密密钥是加密密钥的逆序,就能使得解密算法与加密算法保持一致。SMS4加解密算法的结构完全相同,只是在使用轮密钥时解密密钥是加密密钥的逆序。

S盒是一种利用非线性变换构造的分组密码的一个组件,主要是为了实现分组密码过程中的混淆的特性和设计的。SMS4算法中的S盒在设计之初完全按照欧美分组密码的设计标准进行,它采用的方法是能够很好抵抗差值攻击的仿射函数逆映射复合法。

SM4加密算法应用场景

SM4常用于政府系统的数据传输加密,比如当我们前端向后台传参数的时候,可以使用此算法。对参数的数据进行加密,然后后台对加密的数据进行解密再存储到数据库中,保证数据传输过程中,不受泄露。

本次提供的方案不仅提供sm4的加密解密,还提供了md5算法的完整性防篡改校验。

Java端解决方案

对于java端,我们使用的基于spring的aop切面和自定义注解来实现。整体思路为,当后台开启加密解密的时候,针对于打上注解的方法,寻找实体类中打上注解的字段进行加密和解密。再从前端传递请求的request中取出md5的header,进行md5的完整性,防篡改校验。



首先我们必须说的是两个工具类,一个是SM4Utils工具类,另一个则是md5工具类。

下面先来说一下SM4Utils。这个工具类用于SM4算法的加密和解密及密码校验。我们先直接看代码,然后后面对此进行解释。

SM4Utils

public class Sm4Utils {

    private static final String ENCODING = "UTF-8";
public static final String ALGORIGTHM_NAME = "SM4";
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";
public static final int DEFAULT_KEY_SIZE = 128; public Sm4Utils() {
} static {
Security.addProvider(new BouncyCastleProvider());
} /**
* @Description:生成ecb暗号
*/
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName,BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORIGTHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
} /**
* @Description:自动生成密钥
*/
public static byte[] generateKey() throws Exception {
return generateKey(DEFAULT_KEY_SIZE);
} public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORIGTHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
} /**
* @Description:加密
*/
public static String encryptEcb(String hexKey, String paramStr, String charset) throws Exception {
String cipherText = "";
if (null != paramStr && !"".equals(paramStr)) {
byte[] keyData = ByteUtils.fromHexString(hexKey);
charset = charset.trim();
if (charset.length() <= 0) {
charset = ENCODING;
}
byte[] srcData = paramStr.getBytes(charset);
byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
cipherText = ByteUtils.toHexString(cipherArray);
}
return cipherText;
} /**
* @Description:加密模式之ecb
*/
public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
byte[] bs = cipher.doFinal(data);
return bs;
} /**
* @Description:sm4解密
*/
public static String decryptEcb(String hexKey, String cipherText, String charset) throws Exception {
String decryptStr = "";
byte[] keyData = ByteUtils.fromHexString(hexKey);
byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
charset = charset.trim();
if (charset.length() <= 0) {
charset = ENCODING;
}
decryptStr = new String(srcData, charset);
return decryptStr;
} /**
* @Description:解密
*/
public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
} /**
* @Description:密码校验
*/
public static boolean verifyEcb(String hexKey,String cipherText,String paramStr) throws Exception {
boolean flag = false;
byte[] keyData = ByteUtils.fromHexString(hexKey);
byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] decryptData = decrypt_Ecb_Padding(keyData,cipherData);
byte[] srcData = paramStr.getBytes(ENCODING);
flag = Arrays.equals(decryptData,srcData);
return flag;
} /**
* @Description:测试类
*/
public static void main(String[] args) {
try {
String json = "{\"name\":\"color\",\"sex\":\"man\"}";
// 自定义的32位16进制密钥
String key = "cc9368581322479ebf3e79348a2757d9";
String cipher = Sm4Utils.encryptEcb(key, json,ENCODING);
System.out.println(cipher);
System.out.println(Sm4Utils.verifyEcb(key, cipher, json));
json = Sm4Utils.decryptEcb(key, cipher,ENCODING);
System.out.println(json);
} catch (Exception e) {
e.printStackTrace();
}
} }

我们来仔细看一下上面的代码,首先定义了个4个静态不可修改都变量,用于下方方法的使用。包括编码utf-8,密码名称sm4,密码的分组方式SM4/ECB/PKCS7Padding和默认的key值长度128。

整体的方法我们分为生成ecb暗号,自动生成密钥,加密,解密,密码校验的算法。

以上为sm4utils的核心代码。

MD5Utils


/**
* @Description:md5加密工具
*/
public class Md5Utils { /**
* @Description:获得md5加密串
*/
public static String getMD5String(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
return new BigInteger(1, md.digest()).toString(16);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

MD5Utils主要用于对参数的完整性校验,防止篡改。

此时我们已经实现了主要的加密解密工具,接下来实现spring的aop自定义注解,自定义注解我们要实现三个。

第一个是加密注解,用于方法上的,表示该方法的参数需要被加密。

第二个是解密注解,用于方法上的,表示该方法的参数需要被解密。

第三个是字段加密解密注解,用于标识实体类的字段是否需要被加密和解密。

加密的自定义注解

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Order(-2147483648)
public @interface EncryptMethod {
}

可以看到此时打了一个order注解,-2147483648用于标识优先级最高。

解密的自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Order(-2147483648)
@Inherited
public @interface DecryptMethod {
}

加密解密字段的自定义注解

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Order(-2147483648)
public @interface EncryptField {
}

注解的准备工作已经做完,接下来就是实现sm4的切面方法。不了解spring的aop的实现方法,可以去先补一下spring的相关知识,这里不做赘述。我们这里采用的切面都是环绕通知,切面的切点是加密解密的注解。

加密解密切面的实现

说一下切面的答题实现思路。我们可以看到@ConditionalOnProperty(prefix = "sm4", value = "enable", matchIfMissing = false),这个我们做成了根据配置文件的配置进行动态的开关。

我们在appication.yml文件中进行如下的配置。这样切面是否生效就取决于配置。然后我们捕捉加密的注解和解密的注解,然后对加了注解的方法中的逻辑进行加密和解密。

下方代码的切点方法为EncryptAOPCut和DecryptAOPCut。随后通过around对切点进行捕捉。分别调用的核心的加密算法encryptMethodAop和解密算法decryptMethodAop。

sm4:
enable: true

核心的加密解密算法都是使用环绕通知的ProceedingJoinPoint类,从他的对象中我们可以取到spring的各种参数,包括request请求,请求的参数和response对象。


/**
* @Description:sm4加密解密切面
*/
@Order(-2147483648)
@Aspect
@Component
@ConditionalOnProperty(prefix = "sm4", value = "enable", matchIfMissing = false)
public class Sm4Aspect { private Logger log = LoggerFactory.getLogger(Sm4Aspect.class);
private static final String DEFAULT_KEY = "cc9368581322479ebf3e79348a2757d9"; public Sm4Aspect() {
} @Pointcut("@annotation(com.jichi.aop.sm4.EncryptMethod)")
public void EncryptAOPCut() {
} @Pointcut("@annotation(com.jichi.aop.sm4.DecryptMethod)")
public void DecryptAOPCut() {
} @Around("EncryptAOPCut()")
public Object encryptMethodAop(ProceedingJoinPoint joinPoint) {
Object responseObj = null;
try {
responseObj = joinPoint.proceed();
this.handleEncrypt(responseObj);
//md5加密
String md5Data = Md5Utils.getMD5String(new Gson().toJson(responseObj));
SpringContextUtil.getHttpServletResponse().setHeader("md5",md5Data);
} catch (Throwable throwable) {
throwable.printStackTrace();
this.log.error("encryptMethodAop处理出现异常{}", throwable);
}
return responseObj;
} @Around("DecryptAOPCut()")
public Object decryptMethodAop(ProceedingJoinPoint joinPoint) {
Object responseObj = null;
try {
responseObj = joinPoint.getArgs()[0];
//throw new RuntimeException("md5校验失败");
this.handleDecrypt(responseObj);
String md5 = "";
md5 = Md5Utils.getMD5String(new Gson().toJson(responseObj));
System.out.println(md5);
String origianlMd5 = "";
origianlMd5 = SpringContextUtil.getHttpServletRequest().getHeader("md5");
if(origianlMd5.equals(md5)){
responseObj = joinPoint.proceed();
}else{
this.log.error("参数的md5校验不同,可能存在篡改行为,请检查!");
throw new Exception("参数的md5校验不同,可能存在篡改行为,请检查!");
}
} catch (Throwable throwable) {
throwable.printStackTrace();
this.log.error("decryptMethodAop处理出现异常{}", throwable);
}
return responseObj;
} private void handleEncrypt(Object requestObj) throws Exception {
if (!Objects.isNull(requestObj)) {
Field[] fields = requestObj.getClass().getDeclaredFields();
Field[] fieldsCopy = fields;
int fieldLength = fields.length;
for(int i = 0; i < fieldLength; ++i) {
Field field = fieldsCopy[i];
boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
if (hasSecureField) {
field.setAccessible(true);
String plaintextValue = (String)field.get(requestObj);
String encryptValue = Sm4Utils.encryptEcb(DEFAULT_KEY, plaintextValue, "");
field.set(requestObj, encryptValue);
}
}
}
} private Object handleDecrypt(Object responseObj) throws Exception {
if (Objects.isNull(responseObj)) {
return null;
} else {
Field[] fields = responseObj.getClass().getDeclaredFields();
Field[] fieldsCopy = fields;
int fieldLength = fields.length;
for(int i = 0; i < fieldLength; ++i) {
Field field = fieldsCopy[i];
boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
if (hasSecureField) {
field.setAccessible(true);
String encryptValue = (String)field.get(responseObj);
String plaintextValue = Sm4Utils.decryptEcb(DEFAULT_KEY, encryptValue, "");
field.set(responseObj, plaintextValue);
}
}
return responseObj;
}
}
}

代码实际应用

首先我们可以定义一个实体类,对实体类的字段进行加密或解密的标识。我们这里建立了一个Info实体类,对于其中的name属性,我们加了注解加密解密字段,对于sex属性我们不做任何处理。

@Data
public class Info { @EncryptField
private String name; private String sex; }

然后我们对于controller方法打上加密的方法或解密的方法。

@RestController
@RequestMapping("/demo/test")
public class TestController { @PostMapping("/saveInfo")
@DecryptMethod
public HashMap<String,String> saveInfo(@RequestBody Info info) {
HashMap<String,String> result = new HashMap<String,String>();
String name = info.getName();
System.out.println(name);
String sex= info.getSex();
System.out.println(sex);
result.put("flag","1");
result.put("msg","操作成功");
return result;
} }

注意到方法上的注解@DecryptMethod,以为这着我们的这个方法将会进行解密。如果是@EncryptMethod,则代表对方法进行加密。

总结

到此为止,涉及到java后端的代码解决方案已经完毕。示例代码已经给出,大家可以直接使用,本人亲测有效。文中难免有不足,欢迎大家批评指正。

一文带你学会国产加密算法SM4的java实现方案的更多相关文章

  1. 一文带你学会国产加密算法SM4的vue实现方案

    前言 上篇文章我们介绍了国产SM4加密算法的后端java实现方案.没有看过的小伙伴可以看一下这篇文章. https://www.cnblogs.com/jichi/p/12907453.html 本篇 ...

  2. 一文带你学会java的jvm精华知识点

    前言 本文分为20多个问题,通过问题的方式,来逐渐理解jvm,由浅及深.希望帮助到大家. 1. Java类实例化时,JVM执行顺序? 正确的顺序如下: 1父类静态代码块 2父类静态变量 3子类静态代码 ...

  3. 一文带你学会基于SpringAop实现操作日志的记录

    前言 大家好,这里是经典鸡翅,今天给大家带来一篇基于SpringAop实现的操作日志记录的解决的方案.大家可能会说,切,操作日志记录这么简单的东西,老生常谈了.不! 网上的操作日志一般就是记录操作人, ...

  4. 一文带你学会使用YOLO及Opencv完成图像及视频流目标检测(上)|附源码

    计算机视觉领域中,目标检测一直是工业应用上比较热门且成熟的应用领域,比如人脸识别.行人检测等,国内的旷视科技.商汤科技等公司在该领域占据行业领先地位.相对于图像分类任务而言,目标检测会更加复杂一些,不 ...

  5. 一文带你学会AQS和并发工具类的关系

    1. 存在的意义   AQS(AbstractQueuedSynchronizer)是JAVA中众多锁以及并发工具的基础,其底层采用乐观锁,大量使用了CAS操作, 并且在冲突时,采用自旋方式重试,以实 ...

  6. 一文带你学会AQS和并发工具类的关系2

    1.创建公平锁 1.使用方式 Lock reentrantLock = new ReentrantLock(true); reentrantLock.lock(); //加锁 try{ // todo ...

  7. 【项目实践】一文带你搞定Spring Security + JWT

    以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...

  8. Istio是啥?一文带你彻底了解!

    原标题:Istio是啥?一文带你彻底了解! " 如果你比较关注新兴技术的话,那么很可能在不同的地方听说过 Istio,并且知道它和 Service Mesh 有着牵扯. 这篇文章可以作为了解 ...

  9. 一文带您了解5G的价值与应用

    一文带您了解5G的价值与应用 5G最有趣的一点是:大多数产品都是先有明确应用场景而后千呼万唤始出来.而5G则不同,即将到来的5G不仅再一次印证了科学技术是第一生产力还给不少用户带来了迷茫——我们为什么 ...

随机推荐

  1. JDBC 中的事务和批处理 batch

    JDBC事务处理: 事务处理一般在事务开始前把事务提交设置为false 所有DML语句执行完成后提交事务 demo: package com.xzlf.jdbc; import java.sql.Co ...

  2. 0day学习笔记(3)Windows定位API引起的惨案(原理)

    段选择器FS与TEB WinNT内核下内存采用保护模式,段寄存器的意义与实模式汇编下的意义不同.另外,FS存的是段选择子,而不是实模式下的高16位基地址. FS寄存器指向当前活动线程的TEB结构(线程 ...

  3. beego rel/reverse

    用户可以发布多个文章 对用户来说是一对多 对文章来说是多对一 用户是主表 文章是用户的从表 rel用在从表中,给主表结构体设置主键,也就是文章表对应用户表的外键 reverse用在主表中,指定主表与从 ...

  4. BareTail 观看文件增加的工具

  5. GIT分布式版本控制

    1.1Git简介 linus 用C语言编写 2005年诞生 分布式版本管理系统 速度快,适合大规模,跨地区多人协同开发 Git不仅是一款开源的分布式版本控制系统,而且有其独特的功能特性,例如大多数的分 ...

  6. Blazor WebAssembly 3.2.0 正式起飞,blazor 适合你吗?

    最近blazor更新很快,今天在官方博客上发布了Blazor WebAssembly 3.2.0 RC:https://devblogs.microsoft.com/aspnet/blazor-web ...

  7. Android:RelativeLayout 内容居中

    Android RelativeLayout 内容居中解决办法: 使用Linearlayout本来利用父控件的gravity属性是很好解决的.但是对应RelativeLayout虽然有gravity属 ...

  8. Next.js 7发布,构建速度提升40%

    Next.js团队发布了其开源React框架的7版本.该版本的Next.js主要是改善整体的开发体验,包括启动速度提升57%.开发时的构建速度提升40%.改进错误报告和WebAssembly支持. \ ...

  9. flex学习

            今天看视频学习的时候,发现css有一个 flex 的属性,非常的好用,为了让自己能够熟记,特意来分享一下flex的用法. 首先,采用Flex布局的元素,称为Flex容器(flex co ...

  10. localStorage和cookie的跨域解决方案

    原文转自:点我 前言 localStorage和cookie大家都用过,我前面也有文章介绍过,跨域大家也都了解,我前面也有文章详细描述过.但是localStorage和cookie的跨域问题,好多小伙 ...