SpringBoot+Vue實(shí)現(xiàn)SM4加密傳輸
最近項(xiàng)目中因?yàn)槊鼙R?,需要對敏感?shù)據(jù)加密傳輸,目前就用SpringBoot+Vue實(shí)現(xiàn)SM4加密傳輸,目前只是一個(gè)基礎(chǔ)過渡方案,僅供參考使用。
一、前置準(zhǔn)備
1. 后端SpringBoot:引入BouncyCastle依賴
Java這邊實(shí)現(xiàn)SM4,最常用的就是BouncyCastle這個(gè)加密庫,直接在pom.xml里加依賴就行,我用的是1.70版本,比較穩(wěn)定,沒什么兼容性問題:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>
2. 前端Vue:安裝sm4js插件
前端這邊不用自己造輪子,有現(xiàn)成的sm4js插件可以用,直接npm安裝即可,命令很簡單:
npm install sm4js
二、后端核心:SM4工具類實(shí)現(xiàn)(SpringBoot)
后端的核心就是寫一個(gè)可復(fù)用的SM4工具類,我這里實(shí)現(xiàn)了CBC模式(比ECB模式更安全,需要初始化向量IV),同時(shí)支持Base64編碼(方便網(wǎng)絡(luò)傳輸,比Hex更節(jié)省空間)。
先給大家貼完整代碼,后面再嘮關(guān)鍵要點(diǎn):
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();
// 靜態(tài)代碼塊:注冊BouncyCastle加密提供者
static {
if (isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) {
Security.addProvider(PROVIDER);
}
}
// 填充模式枚舉,方便調(diào)用,常用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默認(rèn)密鑰長度)
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加密(核心業(yè)務(wù)方法,敏感數(shù)據(jù)傳輸用這個(gè))
public static String encryptBase64_CBC(String data, String key) {
// 固定IV(前后端必須一致?。?,也可以動(dòng)態(tài)生成后和密文一起傳輸
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模式:底層字節(jié)數(shù)組加密實(shí)現(xiàn)
@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實(shí)例
@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";
// 敏感數(shù)據(jù)
String sensitiveData = "11111";
// 加密結(jié)果
String encryptedData = SM4Utils.encryptBase64_CBC(sensitiveData, key);
System.out.println("加密后的密文:" + encryptedData);
}
}
后端關(guān)鍵要點(diǎn)說明
- 密鑰和IV的約定:
- 填充模式:這里用了PKCS5Padding,和前端sm4js的默認(rèn)填充模式兼容,避免對接時(shí)出現(xiàn)填充錯(cuò)誤。
- Base64編碼:加密后的字節(jié)數(shù)組轉(zhuǎn)成Base64字符串,方便通過HTTP接口傳輸,不會(huì)出現(xiàn)亂碼問題。
三、前端核心:Vue實(shí)現(xiàn)SM4解密
前端的核心是接收后端返回的Base64密文,通過sm4js插件進(jìn)行解密,還原出原始的敏感數(shù)據(jù)。我這里封裝了一個(gè)工具類,方便在項(xiàng)目的各個(gè)組件中復(fù)用。
1. 可選:全局注冊sm4js(方便全局調(diào)用)
如果項(xiàng)目中很多地方都需要解密,可以在main.js里全局注冊,省去每次引入的麻煩:
import Sm4js from 'sm4js' Vue.prototype.$sm4 = Sm4js
2. 核心:解密工具類封裝
新建一個(gè)sm4Utils.js文件,封裝解密方法,關(guān)鍵是要和后端的密鑰、IV、加密模式、填充模式保持一致,否則解密肯定翻車:
import Sm4js from 'sm4js';
let sm4Utils = {};
// 解密方法:參數(shù)(后端約定的密鑰,后端返回的Base64密文)
sm4Utils.decryptedData = function(key, data){
// sm4配置項(xiàng):和后端完全對應(yīng)!
let sm4Config = {
key: key, // 密鑰:和后端一致(qwegwfdtwer234)
iv: 'Wqsdy3M345BV6GRXB', // IV:和后端固定IV一致
mode: 'cbc', // 加密模式:CBC(和后端一致)
padding: 'PKCS5Padding' // 填充模式:和后端一致(默認(rèn)也可以,保險(xiǎn)起見顯式指定)
}
// 實(shí)例化sm4對象
let sm4 = new Sm4js(sm4Config);
// 關(guān)鍵:后端返回的是Base64密文,先通過atob()解碼,再進(jìn)行sm4解密
return sm4.decrypt(atob(data));
}
export default sm4Utils;
3. 組件中使用解密工具類
在需要解密的Vue組件中,引入封裝好的工具類,直接調(diào)用即可,非常方便:
<template>
<div>
<p>解密后的數(shù)據(jù):{{ decryptedResult }}</p>
</div>
</template>
<script>
// 引入sm4解密工具類
import sm4Utils from '@/utils/sm4Utils.js';
export default {
data() {
return {
decryptedResult: '', // 解密結(jié)果
key: 'qwegwfdtwer234', // 后端約定的密鑰
encryptedData: '' // 后端返回的加密密文(接口請求獲?。?
};
},
mounted() {
// 模擬接口返回密文,調(diào)用解密方法
this.encryptedData = SM4Utils.encryptBase64_CBC("11111","qwegwfdtwer234"); // 后端返回的密文
this.decryptedResult = sm4Utils.decryptedData(this.key, this.encryptedData);
}
};
</script>
四、總結(jié)
該方案其實(shí)對數(shù)據(jù)安全傳輸提升不明顯,后期有較大的提升空間。該方案只能防止「明文傳輸被竊聽」,避免敏感數(shù)據(jù)被「零成本獲取」,是一種「基礎(chǔ)的安全防護(hù)」,比明文傳輸略強(qiáng),但存在較明顯的短板。
核心弊端
- 密鑰安全隱患:前端需持有密鑰,無論硬編碼還是接口獲取,均可能被提取,泄露后加密失效。
- 對稱加密局限:單密鑰加解密,一旦密鑰泄露,攻擊者可解密所有數(shù)據(jù)、偽造加密信息。
- 前端環(huán)境脆弱:瀏覽器/客戶端易被調(diào)試、抓包,輔助防護(hù)僅增加攻擊難度,無法根治泄露風(fēng)險(xiǎn)。
- 適用場景有限:僅能抵御基礎(chǔ)竊聽,無法滿高安全等級(jí)項(xiàng)目的保密需求。
安全提升方案
- 動(dòng)態(tài)生成短期密鑰:為每個(gè)用戶會(huì)話生成臨時(shí)SM4密鑰,綁定會(huì)話有效期,過期自動(dòng)失效,降低密鑰泄露影響范圍。
- 非對稱加密傳密鑰:用SM2/RSA加密臨時(shí)SM4密鑰,前端僅存公鑰(可公開),后端保管私鑰,杜絕密鑰傳輸泄露。
- 密鑰安全存儲(chǔ)與清理:前端密鑰不存localStorage,僅駐留內(nèi)存,會(huì)話結(jié)束(退出/關(guān)頁)立即清空,避免殘留。
- 增加數(shù)據(jù)校驗(yàn)機(jī)制:后端對加密數(shù)據(jù)添加SM3哈希校驗(yàn),前端解密后驗(yàn)證,防止數(shù)據(jù)被篡改偽造。
- 前端代碼加固:開啟代碼混淆、禁止調(diào)試、密鑰分片存儲(chǔ),提升攻擊者提取密鑰和破解邏輯的難度。
- 接口權(quán)限與加密結(jié)合:對獲取密鑰/密文的接口做嚴(yán)格權(quán)限控制,僅授權(quán)用戶可訪問,多重防護(hù)降低風(fēng)險(xiǎn)。
到此這篇關(guān)于SpringBoot+Vue實(shí)現(xiàn)SM4加密傳輸?shù)奈恼戮徒榻B到這了,更多相關(guān)SpringBoot Vue SM4加密傳輸內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java8中的Collectors.groupingBy用法詳解
這篇文章主要介紹了java8中的Collectors.groupingBy用法詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Java統(tǒng)計(jì)接口耗時(shí)的常用方法大全
線上接口突然變慢,用戶投訴不斷,但你卻不知道問題出在哪里?今天我就來分享6種統(tǒng)計(jì)接口耗時(shí)的實(shí)用方法,幫你快速定位性能瓶頸,需要的朋友可以參考下2026-01-01
深入了解Java核心類庫--BigDecimal和System類
這篇文章主要為大家詳細(xì)介紹了javaBigDecimal和System類定義與使用的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能給你帶來幫助2021-07-07
Java 重命名 Excel 工作表并設(shè)置工作表標(biāo)簽顏色的示例代碼
這篇文章主要介紹了Java 重命名 Excel 工作表并設(shè)置工作表標(biāo)簽顏色的示例代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10

