最近项目中因为密保要求,需要对敏感数据加密传输,目前就用springboot+vue实现sm4加密传输,目前只是一个基础过渡方案,仅供参考使用。
一、前置准备
1. 后端springboot:引入bouncycastle依赖
java这边实现sm4,最常用的就是bouncycastle这个加密库,直接在pom.xml里加依赖就行,我用的是1.70版本,比较稳定,没什么兼容性问题:
<dependency> <groupid>org.bouncycastle</groupid> <artifactid>bcprov-jdk15on</artifactid> <version>1.70</version> </dependency>
2. 前端vue:安装sm4js插件
前端这边不用自己造轮子,有现成的sm4js插件可以用,直接npm安装即可,命令很简单:
npm install sm4js
二、后端核心:sm4工具类实现(springboot)
后端的核心就是写一个可复用的sm4工具类,我这里实现了cbc模式(比ecb模式更安全,需要初始化向量iv),同时支持base64编码(方便网络传输,比hex更节省空间)。
先给大家贴完整代码,后面再唠关键要点:
import lombok.getter;
import lombok.sneakythrows;
import lombok.extern.slf4j.slf4j;
import org.bouncycastle.jce.provider.bouncycastleprovider;
import org.bouncycastle.util.encoders.hex;
import javax.crypto.cipher;
import javax.crypto.keygenerator;
import javax.crypto.spec.ivparameterspec;
import javax.crypto.spec.secretkeyspec;
import java.nio.charset.standardcharsets;
import java.security.securerandom;
import java.security.security;
import java.util.base64;
import static java.util.objects.isnull;
@slf4j
public class sm4utils {
private static final int default_key_size = 128;
private static final string algorithm = "sm4";
private static final string sm4_ecb_ = "sm4/ecb/";
private static final string sm4_cbc_ = "sm4/cbc/";
private static final base64.encoder base64_encoder = base64.getencoder();
private static final base64.decoder base64_decoder = base64.getdecoder();
private static final bouncycastleprovider provider = new bouncycastleprovider();
// 静态代码块:注册bouncycastle加密提供者
static {
if (isnull(security.getprovider(bouncycastleprovider.provider_name))) {
security.addprovider(provider);
}
}
// 填充模式枚举,方便调用,常用pkcs5/pkcs7
@getter
public enum padding {
pkcs5("pkcs5padding"),
pkcs7("pkcs7padding"),
iso10126("iso10126padding");
private final string name;
padding(string name) {
this.name = name;
}
}
// 生成base64格式的sm4密钥(128位,sm4默认密钥长度)
public static string genkeyasbase64() {
return genkeyasbase64(default_key_size);
}
public static string genkeyasbase64(int keysize) {
return base64_encoder.encodetostring(genkey(keysize));
}
@sneakythrows
private static byte[] genkey(int keysize) {
keygenerator kg = keygenerator.getinstance(algorithm, bouncycastleprovider.provider_name);
kg.init(keysize, new securerandom());
return kg.generatekey().getencoded();
}
// cbc模式:base64加密(核心业务方法,敏感数据传输用这个)
public static string encryptbase64_cbc(string data, string key) {
// 固定iv(前后端必须一致!),也可以动态生成后和密文一起传输
string fixediv = "wqsdy3m345bv6grxb";
return base64_encoder.encodetostring(encrypt_cbc(
data.getbytes(standardcharsets.utf_8),
key.getbytes(standardcharsets.utf_8),
fixediv.getbytes(standardcharsets.utf_8),
padding.pkcs5
));
}
// cbc模式:底层字节数组加密实现
@sneakythrows
private static byte[] encrypt_cbc(byte[] data, byte[] key, byte[] iv, padding padding) {
cipher cipher = getcipher_cbc(padding);
secretkeyspec secretkeyspec = new secretkeyspec(key, algorithm);
ivparameterspec ivparameterspec = new ivparameterspec(iv);
cipher.init(cipher.encrypt_mode, secretkeyspec, ivparameterspec);
return cipher.dofinal(data);
}
// 获取cbc模式的cipher实例
@sneakythrows
private static cipher getcipher_cbc(padding padding) {
return cipher.getinstance(sm4_cbc_ + padding.name, bouncycastleprovider.provider_name);
}
// 测试方法
public static void main(string[] args) {
// 测试:密钥(前后端一致,可通过genkeyasbase64()生成)
string key = "qwegwfdtwer234";
// 敏感数据
string sensitivedata = "11111";
// 加密结果
string encrypteddata = sm4utils.encryptbase64_cbc(sensitivedata, key);
system.out.println("加密后的密文:" + encrypteddata);
}
}
后端关键要点说明
- 密钥和iv的约定:
- 填充模式:这里用了pkcs5padding,和前端sm4js的默认填充模式兼容,避免对接时出现填充错误。
- base64编码:加密后的字节数组转成base64字符串,方便通过http接口传输,不会出现乱码问题。
三、前端核心:vue实现sm4解密
前端的核心是接收后端返回的base64密文,通过sm4js插件进行解密,还原出原始的敏感数据。我这里封装了一个工具类,方便在项目的各个组件中复用。
1. 可选:全局注册sm4js(方便全局调用)
如果项目中很多地方都需要解密,可以在main.js里全局注册,省去每次引入的麻烦:
import sm4js from 'sm4js' vue.prototype.$sm4 = sm4js
2. 核心:解密工具类封装
新建一个sm4utils.js文件,封装解密方法,关键是要和后端的密钥、iv、加密模式、填充模式保持一致,否则解密肯定翻车:
import sm4js from 'sm4js';
let sm4utils = {};
// 解密方法:参数(后端约定的密钥,后端返回的base64密文)
sm4utils.decrypteddata = function(key, data){
// sm4配置项:和后端完全对应!
let sm4config = {
key: key, // 密钥:和后端一致(qwegwfdtwer234)
iv: 'wqsdy3m345bv6grxb', // iv:和后端固定iv一致
mode: 'cbc', // 加密模式:cbc(和后端一致)
padding: 'pkcs5padding' // 填充模式:和后端一致(默认也可以,保险起见显式指定)
}
// 实例化sm4对象
let sm4 = new sm4js(sm4config);
// 关键:后端返回的是base64密文,先通过atob()解码,再进行sm4解密
return sm4.decrypt(atob(data));
}
export default sm4utils;
3. 组件中使用解密工具类
在需要解密的vue组件中,引入封装好的工具类,直接调用即可,非常方便:
<template>
<div>
<p>解密后的数据:{{ decryptedresult }}</p>
</div>
</template>
<script>
// 引入sm4解密工具类
import sm4utils from '@/utils/sm4utils.js';
export default {
data() {
return {
decryptedresult: '', // 解密结果
key: 'qwegwfdtwer234', // 后端约定的密钥
encrypteddata: '' // 后端返回的加密密文(接口请求获取)
};
},
mounted() {
// 模拟接口返回密文,调用解密方法
this.encrypteddata = sm4utils.encryptbase64_cbc("11111","qwegwfdtwer234"); // 后端返回的密文
this.decryptedresult = sm4utils.decrypteddata(this.key, this.encrypteddata);
}
};
</script>
四、总结
该方案其实对数据安全传输提升不明显,后期有较大的提升空间。该方案只能防止「明文传输被窃听」,避免敏感数据被「零成本获取」,是一种「基础的安全防护」,比明文传输略强,但存在较明显的短板。
核心弊端
- 密钥安全隐患:前端需持有密钥,无论硬编码还是接口获取,均可能被提取,泄露后加密失效。
- 对称加密局限:单密钥加解密,一旦密钥泄露,攻击者可解密所有数据、伪造加密信息。
- 前端环境脆弱:浏览器/客户端易被调试、抓包,辅助防护仅增加攻击难度,无法根治泄露风险。
- 适用场景有限:仅能抵御基础窃听,无法满高安全等级项目的保密需求。
安全提升方案
- 动态生成短期密钥:为每个用户会话生成临时sm4密钥,绑定会话有效期,过期自动失效,降低密钥泄露影响范围。
- 非对称加密传密钥:用sm2/rsa加密临时sm4密钥,前端仅存公钥(可公开),后端保管私钥,杜绝密钥传输泄露。
- 密钥安全存储与清理:前端密钥不存localstorage,仅驻留内存,会话结束(退出/关页)立即清空,避免残留。
- 增加数据校验机制:后端对加密数据添加sm3哈希校验,前端解密后验证,防止数据被篡改伪造。
- 前端代码加固:开启代码混淆、禁止调试、密钥分片存储,提升攻击者提取密钥和破解逻辑的难度。
- 接口权限与加密结合:对获取密钥/密文的接口做严格权限控制,仅授权用户可访问,多重防护降低风险。
到此这篇关于springboot+vue实现sm4加密传输的文章就介绍到这了,更多相关springboot vue sm4加密传输内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论