(一篇了解国密加密的文章) Vue + Springboot 前端和后端使用国密算法 SM2 SM3 SM4 进行数据加密和传输 完整解决方案
最编程
2024-07-10 15:39:28
...
(一篇文章搞懂国密加密)Vue + Springboot 前后端使用国密算法 SM2 SM3 SM4 进行数据加密传输 完整解决方案
一般国企、事业单位类项目部署到外网时候,常常会用到数据加密,目前现有技术方案, 经常会出现版本插件版本和脚手架版本不一致,导致引入失败。以下示例采用最新vue-cli及spring-boot3进行编写,可以直接进行使用,需要示例代码,可以留言或者私信。
1. 后端依赖
主要依赖
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.23</version>
</dependency>
</dependencies>
2. 前端依赖
"dependencies": {
"gm-crypt": "0.0.2",
"sm-crypto": "^0.3.13",
"vue": "^2.6.14",
"element-ui": "^2.15.14",
}
3. SM3加密
3.1 直接对文件加密
前端代码:
<template>
<input type="file" ref="fileInput" @change="handleFileChange" />
<el-button @click="encryptFile" type="primary">加密</el-button>
</template>
methods: {
handleFileChange(event) {
this.file = event.target.files[0];
},
encryptFile() {
if (!this.file) {
alert("Please select a file.");
return;
}
// 1. sm3文件加密(无密钥)
const reader = new FileReader();
reader.onload = (e) => {
const fileContent = e.target.result;
const hash = smcrypto.sm3(fileContent);
console.log(hash); // 加密结果示例:610c2432d36e242b63297cb21afd0ead7c94ed810fd19e871ed225dc85178f9e
this.encryptedDataSM3NoKey = hash;
};
reader.readAsText(this.file);
},
}
后端代码:
@PostMapping(value = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
@Operation(summary = "直接加密")
public String handleFileUpload(
@RequestPart(value = "Upload files", required = false) MultipartFile file) {
if (file.isEmpty()) {
return "File is empty";
}
try (InputStream inputStream = file.getInputStream()) {
// Save the file and get its path
String utf8Content = convertToUTF8(inputStream);
// Encrypt the file with SM3
String encryptedData = encryptWithSM3(utf8Content);
// 610c2432d36e242b63297cb21afd0ead7c94ed810fd19e871ed225dc85178f9e
return "Encrypted Data: " + encryptedData;
} catch (IOException e) {
return "Failed to process the file: " + e.getMessage();
}
}
3.2 使用秘钥对文件加密
前端代码:
<template>
<input type="file" ref="fileInput" @change="handleFileChange" />
<el-button @click="encryptFileSM3" type="primary">加M</el-button>
</template>
methods: {
handleFileChange(event) {
this.file = event.target.files[0];
},
encryptFileSM3() {
if (!this.file) {
alert("Please select a file.");
return;
}
const reader = new FileReader();
reader.onload = () => {
const uint = new Uint8Array(reader.result);
// 使用 SM3 加密文件内容
const hash = smcrypto.sm3(uint, {
mode: "hmac",
key: "123456789",
});
this.encryptedDataSM3 = hash;
};
reader.readAsArrayBuffer(this.file); // 加密结果示例: c0760f71661088afbf3c56cd1f860da06d0801d625c5cf87d86f197bf67956d6
},
}
后端代码:
@PostMapping(value = "/keyUpload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
@Operation(summary = "密钥加密(123456789)")
public String handleKeyFileUpload(
@RequestPart(value = "Upload files", required = false) MultipartFile file) throws FileNotFoundException {
try (InputStream inputStream = file.getInputStream()) {
String fileHash = SmUtil.hmacSm3(HexUtil.decodeHex("123456789")).digestHex(inputStream);
return "Encrypted Data: " + fileHash;
} catch (IOException e) {
return "Failed to process the file: " + e.getMessage();
}
}
4. SM2
前端代码:
const sm2 = require("sm-crypto").sm2;
let keypair = sm2.generateKeyPairHex();
// 公钥
let publicKey = keypair.publicKey.toUpperCase();
// 私钥
let privateKey = keypair.privateKey.toUpperCase();
this.encryptedDataSM2 = sm2.doEncrypt(this.dataSM2, publicKey);
this.docryptedDataSM2 = sm2.doDecrypt(this.encryptedDataSM2, privateKey);
后端代码:
/**
* 生成秘钥对
*
* @return 公钥和私钥
*/
@Operation(summary = "生成秘钥对")
@GetMapping(value = "/generator")
public static Map<String, String> generator() {
SM2 sm2 = SmUtil.sm2();
String publicKey = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false)).toUpperCase();
String privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey())).toUpperCase();
return new HashMap<String, String>(2) {{
put("publicKey", publicKey);
put("privateKey", privateKey);
}};
}
/**
* 加密
*
* @param publicKey 公钥
* @param data 明文
* @return 密文
*/
@Operation(summary = "加密")
@PostMapping(value = "/encrypt")
public static String encrypt(@Parameter(name = "publicKey", description = "公钥", in = ParameterIn.QUERY, required = true) @RequestParam String publicKey,
@Parameter(name = "data", description = "明文", in = ParameterIn.QUERY, required = true) @RequestParam String data) {
return SmUtil.sm2(null, publicKey)
// 不写默认就是C1C3C2
.setMode(SM2Engine.Mode.C1C3C2)
.encryptHex(data.getBytes(), KeyType.PublicKey)
// 加密后,密文前面会有04,需要去掉
.substring(2);
}
/**
* 解M
*
* @param privateKey 私钥
* @param data M文
* @return 明文
*/
@Operation(summary = "解密")
@PostMapping(value = "/decrypt")
public static String decrypt(@Parameter(name = "privateKey", description = "私钥", in = ParameterIn.QUERY, required = true) @RequestParam String privateKey,
@Parameter(name = "data", description = "M文", in = ParameterIn.QUERY, required = true) @RequestParam String data) {
// 确定前端不会加04,所以后端直接加(上面处理方式可能造成报错(Invalid point coordinates):原因前端加密后M文自带04开头)
data = "04" + data;
return SmUtil.sm2(privateKey, null)
// 不写默认就是C1C3C2
.setMode(SM2Engine.Mode.C1C3C2)
.decryptStr(data, KeyType.PrivateKey);
}
5. SM4 && SM2混合加密
加M过程梳理
- 后端准备sm2公钥和私钥
- 后端将SM2公钥传输到前端
- 前端生成SM4M钥
- 前端使用SM2公钥加MSM4秘钥,获得密文
- 使用SM4秘钥加密数据
- 将密文和加密数据传输至后端
- 后端使用SM2私钥解密密文获得,SM4秘钥
- 使用SM4秘钥解密加密数据,获取真实传输数据信息
前端代码:
<template>
<el-card shadow="hover">
<div slot="header" class="clearfix">
<span>sm2&&sm4示例:</span>
</div>
<el-button
@click="encryptFileSM4BySM2"
style="margin-left: 50px"
type="primary"
>加密</el-button
>
<p style="width: 90vw; word-wrap: break-word; color: red">
密钥随机密钥(sm4): <span style="color: #333333">{{ sm4Key }}</span>
</p>
<p style="width: 90vw; word-wrap: break-word; color: red">
公钥(sm2): <span style="color: #333333">{{ sm2PublicKey }}</span>
</p>
<p style="color: red; word-wrap: break-word">
SM4秘钥加密结果(by-sm2):
<span style="color: #333333">{{ docryptedDataSM4BySM2 }}</span>
</p>
<el-input
placeholder="请输入加密内容"
style="width: 200px"
v-model="dataSM4"
/>
<el-button
@click="decryptFileSM4"
style="margin-left: 50px"
type="primary"
>加密(sm4)</el-button
>
<p style="color: red; word-wrap: break-word">
加密结果(sm4):
<span style="color: #333333">{{ decryptedDataSM4 }}</span>
</p>
</el-card>
</template>
methods: {
encryptFileSM4BySM2() {
const sm2 = require("sm-crypto").sm2;
this.docryptedDataSM4BySM2 = sm2.doEncrypt(
this.sm4Key,
this.sm2PublicKey
);
},
decryptFileSM4() {
const SM4 = require("gm-crypt").sm4;
const pwdKey = this.sm4Key; //密钥 前后端一致,后端提供
let sm4Config = {
key: pwdKey,
mode: "ecb", // 加密的方式有两种,ecb和cbc两种,看后端如何定义的,cbc需要iv参数,ecb不用
iv: "1234567891011121", // 初始向量,cbc模式的第二个参数,也需要跟后端配置的一致
cipherType: "base64",
};
const sm4Util = new SM4(sm4Config); // new一个sm4函数,将上面的sm4Config作为参数传递进去。
this.decryptedDataSM4 = sm4Util.encrypt(this.dataSM4, pwdKey);
}
}
后端代码:
package com.example.springboot3.controller;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.ECKeyUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.nio.charset.StandardCharsets;
@Tag(name = "sm4")
@RestController
@Slf4j
public class EncryptSM4Controller {
// 公钥:这个是前后端加密用的,不压缩选择带04的,不带04到时候前端会报错
private static String publicKey = "04F8BA2A9DFDE5977DFDE3C87A3D0298809FF3396BD908B01DE7057EE4951CF4F193EB0841DA05D7612D13A13E23C0ACB8A00902C0D409236A92C4EF3AA2C72823"; // 私钥:这个保存好,切记不要泄漏,真的泄露了就重新生成一下;
private static String privateKey = "3AEAE64C481550DF7D50B6A693378D0C3722947DFFBD55B43880912497126620";
/**
* 生成秘钥对
*
* @return 公钥和私钥
*/
@Operation(summary = "sm2生成秘钥对")
@GetMapping(value = "/generateCommonKey")
public static Map<String, String> generateCommonKey() {
SM2 sm2 = SmUtil.sm2();
return new HashMap<String, String>(2) {{
put("publicKey", publicKey);
put("privateKey", privateKey);
}};
}
/**
* 解密
*
* @param data 密文
* @return 明文
*/
@Operation(summary = "sm2解M(获取sm4的key)")
@PostMapping(value = "/decryptSm2")
public static String decrypt(String data) {
data = "04" + data;
return SmUtil.sm2(privateKey, null)
// 不写默认就是C1C3C2
.setMode(SM2Engine.Mode.C1C3C2)
.decryptStr(data, KeyType.PrivateKey);
}
/**
* 加密
*
* @param sm4Key 秘钥
* @param data 明文
* @return 密文
*/
@Operation(summary = "sm4加密")
@PostMapping(value = "/encryptSm4")
public static String encryptSm4(String sm4Key, String data) {
SM4 sm4 = SmUtil.sm4(sm4Key.getBytes());
String encrypted = sm4.encryptBase64(data);
return encrypted;
}
/**
* 解密
*
* @param sm4Key 秘钥
* @param data 明文
* @return 密文
*/
@Operation(summary = "sm4解密")
@PostMapping(value = "/decryptSm4")
public static String decryptSm4(String sm4Key, String data) {
SymmetricCrypto sm4 = SmUtil.sm4(sm4Key.getBytes());
byte[] decrypted = sm4.decrypt(data);
// 将字节数组转换为字符串
String decryptedString = new String(decrypted, StandardCharsets.UTF_8);
return decryptedString;
}
}
相关文章
【1】 Vue + Springboot 前后端完整使用国密算法 SM2 数据加密 传输 交互 完整解决方案
【2】 Vue和Springboot实现SM4加密和解密(前、后端均可)很新
【3】java/vue使用国密sm2、sm3、sm4进行数据加密
【4】前端使用国密sm2和sm4进行加解密