当前位置: 首页 > news >正文

Java 使用国密算法实现数据加密传输

本文是混合加密:前端 SM2 + SM4,后端 Spring Boot + Hutool 解密的完整示例。

方案的逻辑是:

  • 前端随机生成一个SM4 key

  • SM4加密整个业务 JSON

  • 用后端提供的SM2 公钥加密这个 SM4 key

  • 后端先用SM2 私钥解出 SM4 key

  • 再用SM4解出业务 JSON

Hutool 官方文档明确支持SM2 / SM3 / SM4,并给出了SmUtil.sm2(...)SmUtil.sm4(...)以及encryptHex / decryptStr这类用法;同时文档说明国密算法需要引入 Bouncy Castle 依赖。sm-crypto系列前端库也支持SM2 / SM3 / SM4。(Hutool)

方案统一用:

  • 前端公钥:SM2 原始公钥 Hex,04 + X + Y

  • SM2 密文:Hex

  • SM4 密文:Hex

  • SM4 key:16 字节字符串

  • SM2 模式C1C3C2


一、前后端协议

前端原始数据:

{ "username": "admin", "password": "123456", "timestamp": 1710000000000 }

前端最终提交给后端:

{ "key": "SM2加密后的SM4密钥(hex)", "data": "SM4加密后的业务JSON(hex)" }


二、后端 Spring Boot 代码

1)Maven 依赖

<dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Hutool --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.29</version> </dependency> <!-- Bouncy Castle,Hutool 国密依赖 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> <version>1.83</version> </dependency> </dependencies>

Hutool 的国密文档明确写了 SM2/SM3/SM4 依赖 Bouncy Castle;Hutool 加密模块文档也说明其封装入口之一就是SmUtil。(Hutool)


2)启动类

package com.example.demo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.security.Security; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); SpringApplication.run(DemoApplication.class, args); } }


3)密钥工具类

这个类负责:

  • 生成 SM2 密钥对

  • 导出前端可用的原始公钥 Hex

  • 导出后端解密用的原始私钥 Hex

Sm2KeyUtil.java

package com.example.demo.crypto; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.SM2; import org.bouncycastle.jce.interfaces.BCECPrivateKey; import org.bouncycastle.jce.interfaces.BCECPublicKey; import org.bouncycastle.math.ec.ECPoint; public class Sm2KeyUtil { private Sm2KeyUtil() { } public static SM2 generateSm2() { return SmUtil.sm2(); } /** * 前端 sm-crypto 可直接使用的公钥: * 04 + X(64位hex) + Y(64位hex) */ public static String getPublicKeyHexForFrontend(SM2 sm2) { BCECPublicKey publicKey = (BCECPublicKey) sm2.getPublicKey(); ECPoint point = publicKey.getQ(); String x = leftPad64(point.getAffineXCoord().toBigInteger().toString(16)); String y = leftPad64(point.getAffineYCoord().toBigInteger().toString(16)); return "04" + x + y; } /** * 后端原始私钥 hex,64位 */ public static String getPrivateKeyHexRaw(SM2 sm2) { BCECPrivateKey privateKey = (BCECPrivateKey) sm2.getPrivateKey(); return leftPad64(privateKey.getD().toString(16)); } /** * 按原始私钥重建 SM2 对象 */ public static SM2 buildSm2ByPrivateKeyHex(String privateKeyHex) { return SmUtil.sm2(privateKeyHex, null); } private static String leftPad64(String hex) { if (hex == null) { return null; } if (hex.length() >= 64) { return hex; } return "0".repeat(64 - hex.length()) + hex; } }

Hutool 官方文档明确区分了 SM2 密钥的几种格式:私钥可为 D 值、PKCS#8、PKCS#1,公钥可为 Q 值、X.509、PKCS#1,并说明新版本构造方法对这些格式做了兼容。文档还给出了用私钥 D 值和公钥 Q 值构建/验签的示例。(Hutool)


4)密钥持有类

演示用启动时生成。生产环境要固定保存,不要每次重启都换。

Sm2KeyHolder.java

package com.example.demo.crypto; import cn.hutool.crypto.asymmetric.SM2; import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; @Component public class Sm2KeyHolder { private String publicKeyHexForFrontend; private String privateKeyHexRaw; private SM2 sm2; @PostConstruct public void init() { this.sm2 = Sm2KeyUtil.generateSm2(); this.publicKeyHexForFrontend = Sm2KeyUtil.getPublicKeyHexForFrontend(sm2); this.privateKeyHexRaw = Sm2KeyUtil.getPrivateKeyHexRaw(sm2); System.out.println("=== SM2密钥初始化 ==="); System.out.println("前端公钥Hex: " + publicKeyHexForFrontend); System.out.println("后端私钥Hex: " + privateKeyHexRaw); } public String getPublicKeyHexForFrontend() { return publicKeyHexForFrontend; } public String getPrivateKeyHexRaw() { return privateKeyHexRaw; } public SM2 getSm2() { return sm2; } }


5)请求 DTO

EncryptedLoginRequest.java

package com.example.demo.dto; public class EncryptedLoginRequest { /** * SM2加密后的SM4 key(hex) */ private String key; /** * SM4加密后的业务数据(hex) */ private String data; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getData() { return data; } public void setData(String data) { this.data = data; } }

LoginPlainRequest.java

package com.example.demo.dto; public class LoginPlainRequest { private String username; private String password; private Long timestamp; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Long getTimestamp() { return timestamp; } public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } }


6)解密服务

HybridCryptoService.java

package com.example.demo.service; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.SM2; import cn.hutool.crypto.symmetric.SM4; import com.example.demo.crypto.Sm2KeyHolder; import com.example.demo.crypto.Sm2KeyUtil; import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; @Service public class HybridCryptoService { private final Sm2KeyHolder keyHolder; public HybridCryptoService(Sm2KeyHolder keyHolder) { this.keyHolder = keyHolder; } /** * 用后端私钥解密前端传来的 SM4 key */ public String decryptSm4Key(String encryptedSm4KeyHex) { SM2 sm2 = Sm2KeyUtil.buildSm2ByPrivateKeyHex(keyHolder.getPrivateKeyHexRaw()); byte[] keyBytes = sm2.decryptFromBcd(encryptedSm4KeyHex, KeyType.PrivateKey); return StrUtil.utf8Str(keyBytes); } /** * 用 SM4 key 解密业务数据 */ public String decryptBusinessData(String sm4Key, String encryptedDataHex) { SM4 sm4 = SmUtil.sm4(sm4Key.getBytes(StandardCharsets.UTF_8)); return sm4.decryptStr(encryptedDataHex, StandardCharsets.UTF_8); } }

Hutool 官方文档给出了SmUtil.sm4(key)encryptHex(...)decryptStr(...)的 SM4 用法,也给出了sm2.decryptFromBcd(..., KeyType.PrivateKey)的 SM2 私钥解密示例。(Hutool)


7)控制器

LoginController.java

package com.example.demo.controller; import cn.hutool.json.JSONUtil; import com.example.demo.crypto.Sm2KeyHolder; import com.example.demo.dto.EncryptedLoginRequest; import com.example.demo.dto.LoginPlainRequest; import com.example.demo.service.HybridCryptoService; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api") public class LoginController { private final Sm2KeyHolder keyHolder; private final HybridCryptoService hybridCryptoService; public LoginController(Sm2KeyHolder keyHolder, HybridCryptoService hybridCryptoService) { this.keyHolder = keyHolder; this.hybridCryptoService = hybridCryptoService; } /** * 提供前端可直接使用的 SM2 原始公钥 */ @GetMapping("/public-key") public Map<String, Object> getPublicKey() { Map<String, Object> result = new HashMap<>(); result.put("code", 0); result.put("publicKey", keyHolder.getPublicKeyHexForFrontend()); return result; } /** * 混合加密登录接口 */ @PostMapping("/login") public Map<String, Object> login(@RequestBody EncryptedLoginRequest request) { Map<String, Object> result = new HashMap<>(); try { // 1. 解密 SM4 key String sm4Key = hybridCryptoService.decryptSm4Key(request.getKey()); // 2. 解密业务 JSON String plainJson = hybridCryptoService.decryptBusinessData(sm4Key, request.getData()); // 3. 转换为明文请求对象 LoginPlainRequest loginRequest = JSONUtil.toBean(plainJson, LoginPlainRequest.class); // 4. 演示校验 if ("admin".equals(loginRequest.getUsername()) && "123456".equals(loginRequest.getPassword())) { result.put("code", 0); result.put("message", "登录成功"); } else { result.put("code", 401); result.put("message", "用户名或密码错误"); } // 生产环境不要打印明文 // System.out.println("解密后JSON: " + plainJson); } catch (Exception e) { result.put("code", 500); result.put("message", "解密失败: " + e.getMessage()); } return result; } }


三、前端代码

这里是通用 JS,Vue / React / 原生都能直接使用。

1)安装依赖

使用sm-crypto,也可以用更新一点的sm-crypto-v2。npm 上显示sm-crypto-v2近期仍有更新,并明确支持 SM2/SM3/SM4。下面示例先按sm-crypto风格来写。(NPM)

npm install sm-crypto


2)混合加密工具

hybrid-login.js

import { sm2, sm4 } from "sm-crypto"; /** * 生成 16 字节 SM4 key * 这里用 16 个 ASCII 字符,后端按 UTF-8 字节拿到就是 16 字节 */ function randomSm4Key(length = 16) { const chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; let result = ""; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } /** * 获取后端提供的 SM2 公钥(原始hex,04开头) */ export async function getPublicKey() { const resp = await fetch("/api/public-key"); const json = await resp.json(); return json.publicKey; } /** * 混合加密: * 1. 随机生成 SM4 key * 2. 用 SM4 加密整个业务 JSON * 3. 用 SM2 公钥加密 SM4 key */ export async function encryptLoginPayload(username, password) { const publicKey = await getPublicKey(); // 1. 随机 SM4 key const sm4Key = randomSm4Key(16); // 2. 原始业务 JSON const payload = JSON.stringify({ username, password, timestamp: Date.now(), }); // 3. SM4 加密业务 JSON(输出 hex) const encryptedData = sm4.encrypt(payload, sm4Key); // 4. SM2 加密 SM4 key(cipherMode=1 表示 C1C3C2) const cipherMode = 1; const encryptedKey = sm2.doEncrypt(sm4Key, publicKey, cipherMode); return { key: encryptedKey, data: encryptedData, }; } /** * 提交登录 */ export async function login(username, password) { const body = await encryptLoginPayload(username, password); const resp = await fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), }); return await resp.json(); }

sm-crypto/同类包支持 SM2、SM4;Hutool 文档则说明SM4可以使用自定义 key,并通过encryptHex/decryptStr处理字符串数据。(NPM)


3)页面调用示例

import { login } from "./hybrid-login"; async function submitLogin() { const username = document.getElementById("username").value; const password = document.getElementById("password").value; const result = await login(username, password); console.log(result); }


四、完整交互过程

1)前端获取公钥

请求:

GET /api/public-key

响应:

{ "code": 0, "publicKey": "04xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }

这个公钥是给前端sm2.doEncrypt(...)直接用的原始 SM2 公钥。


2)前端组装明文 JSON

{ "username": "admin", "password": "123456", "timestamp": 1710000000000 }


3)前端生成随机 SM4 key

例如:

A8cD3eF7hJ2kL9mN


4)前端加密

  • data = sm4.encrypt(payload, sm4Key)

  • key = sm2.doEncrypt(sm4Key, publicKey, 1)

最终请求体:

{ "key": "SM2加密后的SM4密钥(hex)", "data": "SM4加密后的业务JSON(hex)" }


5)后端解密

  • 用 SM2 私钥解出sm4Key

  • 用 SM4 key 解出plainJson

  • 解析出username/password/timestamp


五、为什么这样更合理

“为什么不直接用 SM2”。这里混合加密的优势就是:

  • SM2负责保护一个很小的随机密钥

  • SM4负责高效加密真正的业务数据

Hutool 文档本身也把 SM2 归为非对称加密,把 SM4 归为对称加密;这两类算法在工程上本来就常常配合使用。(Hutool)


六、最容易踩的坑

1. 前端公钥格式错

不能把getPublicKeyBase64()直接给前端。
前端要的是04 + X + Y的原始公钥,不是 X.509/ASN.1 编码的公钥。Hutool 文档明确区分了公钥的Q 值X.509两种不同格式。(Hutool)

2. SM2 模式不一致

前端这里固定:

const cipherMode = 1;

联调时就按C1C3C2统一,不要混。

3. SM4 key 长度不对

Hutool 文档中自定义 SM4 key 的示例是128 位,也就是 16 字节。这里前端随机生成 16 个 ASCII 字符,后端按 UTF-8 读取后恰好是 16 字节。(Hutool)

4. 后端每次重启重新生成密钥

演示可以这样。生产不行。
生产环境要把私钥固定存起来,不然前端今天拿到的公钥和明天后端的私钥就不是一对了。

5. 仍然必须用 HTTPS

这套字段级加密不能替代 TLS。Hutool 只解决加解密实现,不负责传输层安全。(Hutool)


七、生产版建议

可以先用上面代码跑通,之后再补这几项:

  • 固定私钥:放配置中心 / KMS / HSM

  • 时间戳校验:比如 5 分钟内有效

  • nonce 防重放

  • 签名校验:在混合加密外再加签,防篡改

  • 不要打印明文 JSON / 密码

  • 全站 HTTPS


八、最小可验证步骤

先启动后端。

第一步,调用:

GET /api/public-key

确认返回的publicKey04开头的长 hex 字符串。

第二步,前端执行:

const body = await encryptLoginPayload("admin", "123456"); console.log(body);

应该能看到:

{ "key": "一串SM2 hex密文", "data": "一串SM4 hex密文" }

第三步,调用:

login("admin", "123456")

应该返回:

{ "code": 0, "message": "登录成功" }

http://www.jsqmd.com/news/561687/

相关文章:

  • 2025嵌入式开发新范式:用Rust告别C语言内存陷阱的实战指南
  • YOLO X Layout实战:商业报告智能解析,快速提取表格与图表数据
  • 从零到一:基于LoRA与vLLM的Qwen3-0.6B轻量化微调与本地推理实战
  • 极空间+Docker轻松打造个人电子书库:TaleBook与豆瓣刮削器实战指南
  • PaddleOCR实战指南:从Python快速入门到C++高效部署
  • 字节跳动的Trae的使用感受,及对比腾讯小龙虾使用场景
  • 原神帧率解锁技术突破:从性能瓶颈到效能释放的全流程优化指南
  • WebSocket vs REST:股票行情数据接口怎么选?附AllTick接入避坑指南
  • Microsoft Defender SmartScreen检测关闭【亲测有效】
  • 重塑数据可视化:突破传统图表限制的创意解决方案
  • 大学思政课高分通关秘籍:我用思维导图搞定马原期末考试(附全套笔记模板)
  • BM3D算法深度解析:为什么它至今仍是图像去噪的黄金标准?
  • 格密码学入门:从基础定义到核心困难问题解析
  • langgraph笔记
  • Guohua Diffusion 数据库设计实战:从概念到实现的课程设计参考
  • DW_apb_uart初始化全流程解析:从时钟门控到中断配置的15个关键步骤
  • 2026专业无线图传品牌哪个最好?猛玛极影Ultra登顶榜首
  • Redis 持久化与高可用:RDB/AOF、主从复制、哨兵与一致性取舍
  • LinkSwift网盘直链下载助手:2025年高效下载终极解决方案
  • Fusion Compiler vs Innovus:5nm芯片设计实战对比,哪个更适合你的项目?
  • 认知迷雾计划:用废话消耗AI算力
  • 高效掌握开源工具抖音直播录制:从基础搭建到高级应用指南
  • OpenClaw如何安装?2026年本地萌新4分钟部署+阿里云百炼API配置保姆级方法
  • 构建专属数字分身:Duix-Avatar本地化部署与应用全指南
  • 革新性移动优先界面重构:Luci-Theme-Neobird重新定义路由器管理体验
  • 计算机毕业设计:车主之家汽车销量爬虫分析平台 Flask框架 requests爬虫 可视化 车辆 大数据 机器学习 hadoop(建议收藏)✅
  • 网易云无损解析工具深度指南:打造高品质音乐收藏全攻略
  • 从HikariCP连接泄漏告警到业务逻辑耗时优化实战
  • OpenClaw怎么搭建?2026年云端小白3分钟集成+阿里云百炼API配置喂奶级流程
  • 蒙阴浩翔工匠丨专业家电清洗、拆卸、清洗、安装一站式服务 - 宁夏壹山网络