一文带你学会国产加密算法SM4的java实现方案
前言
今天给大家带来一个国产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实现方案的更多相关文章
- 一文带你学会国产加密算法SM4的vue实现方案
前言 上篇文章我们介绍了国产SM4加密算法的后端java实现方案.没有看过的小伙伴可以看一下这篇文章. https://www.cnblogs.com/jichi/p/12907453.html 本篇 ...
- 一文带你学会java的jvm精华知识点
前言 本文分为20多个问题,通过问题的方式,来逐渐理解jvm,由浅及深.希望帮助到大家. 1. Java类实例化时,JVM执行顺序? 正确的顺序如下: 1父类静态代码块 2父类静态变量 3子类静态代码 ...
- 一文带你学会基于SpringAop实现操作日志的记录
前言 大家好,这里是经典鸡翅,今天给大家带来一篇基于SpringAop实现的操作日志记录的解决的方案.大家可能会说,切,操作日志记录这么简单的东西,老生常谈了.不! 网上的操作日志一般就是记录操作人, ...
- 一文带你学会使用YOLO及Opencv完成图像及视频流目标检测(上)|附源码
计算机视觉领域中,目标检测一直是工业应用上比较热门且成熟的应用领域,比如人脸识别.行人检测等,国内的旷视科技.商汤科技等公司在该领域占据行业领先地位.相对于图像分类任务而言,目标检测会更加复杂一些,不 ...
- 一文带你学会AQS和并发工具类的关系
1. 存在的意义 AQS(AbstractQueuedSynchronizer)是JAVA中众多锁以及并发工具的基础,其底层采用乐观锁,大量使用了CAS操作, 并且在冲突时,采用自旋方式重试,以实 ...
- 一文带你学会AQS和并发工具类的关系2
1.创建公平锁 1.使用方式 Lock reentrantLock = new ReentrantLock(true); reentrantLock.lock(); //加锁 try{ // todo ...
- 【项目实践】一文带你搞定Spring Security + JWT
以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...
- Istio是啥?一文带你彻底了解!
原标题:Istio是啥?一文带你彻底了解! " 如果你比较关注新兴技术的话,那么很可能在不同的地方听说过 Istio,并且知道它和 Service Mesh 有着牵扯. 这篇文章可以作为了解 ...
- 一文带您了解5G的价值与应用
一文带您了解5G的价值与应用 5G最有趣的一点是:大多数产品都是先有明确应用场景而后千呼万唤始出来.而5G则不同,即将到来的5G不仅再一次印证了科学技术是第一生产力还给不少用户带来了迷茫——我们为什么 ...
随机推荐
- 改善 Python 程序的 91 个建议
1.引论 建议1:理解Pythonic概念—-详见Python中的<Python之禅> 建议2:编写Pythonic代码 避免不规范代码,比如只用大小写区分变量.使用容易混淆的变量名.害怕 ...
- Java中的字符串操作(比较String,StringBuiler和StringBuffer)
一.前言 刚开始学习Java时,作为只会C语言的小白,就为其中的字符串操作而感到震撼.相比之下,C语言在字节数组中保存一个结尾的\0去表示字符串,想实现字符串拼接,还需要调用strcpy库函数或者自己 ...
- 挑战全网最幽默的Vuex系列教程:第六讲 Vuex的管理员Module(实战篇)
写在前面 这一讲是 Vuex 基础篇的最后一讲,也是最为复杂的一讲.如果按照官方来的话,对于新手可能有点难以接受,所以想了下,决定干脆多花点时间,用一个简单的例子来讲解,顺便也复习一下之前的知识点. ...
- sql注入原理+mysql相关知识点
什么是SQL注入 sql就是经常说的数据库,而sql注入就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令.SQL注入是比较常见的网络攻击 ...
- springboot+dubbo简单分布式RPC调用demo
使用springboot+dubbo搭建RPC入门案例 本文背景简述: 最近在学习公司的一套RPC框架,初步接触的时候感觉挺复杂的.但是知道其原理肯定是和dubbo很相似的,毕竟都是RPC框架嘛,只是 ...
- wait,waitpid的学习使用
man wait man waitpid 从中可知 函数原型 pid_t wait(int* status); pid_t waitpid(pid_t pid, int* status, int op ...
- EVE模拟器的配置
(注:本文整理自达叔的EVE模拟器使用说明https://blog.51cto.com/dashu666/1971728) 基础部署篇 所需要准备的东西: 1.VMWare (虚拟化软件,用来承载模拟 ...
- IDEA设置导入主题样式皮肤,加入背景图片
主题下载地址:http://www.riaway.com/theme.php 里面有很多主题,看个人喜好,这里我用的Monokai Sublime Text 3. 导入主题打开IDEA,找到File ...
- Win10 及 Google 浏览器显示界面异常
win10 和 google 界面显示异常 win10 个别 ui 组件花屏,google 界面直接黑屏 解决方式 更新集成显卡或者重装显卡驱动,最好使用 驱动人生 !!!
- java 8 stream中的Spliterator简介
目录 简介 tryAdvance trySplit estimateSize characteristics 举个例子 总结 java 8 stream中的Spliterator简介 简介 Split ...