当前位置: 代码网 > it编程>编程语言>Java > SpringBoot算法实现数据加密传输

SpringBoot算法实现数据加密传输

2026年03月29日 Java 我要评论
本文是混合加密:前端 sm2 + sm4,后端 spring boot + hutool 解密的完整示例。方案的逻辑是:前端随机生成一个 sm4 key用 sm4 加密整个业务 json用后端提供的

本文是混合加密:前端 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

方案统一用:

  • 前端公钥: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 代码

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

启动类

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);
    }
}

密钥工具类

这个类负责:

  • 生成 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 值构建/验签的示例。

密钥持有类

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

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;
    }
}

请求 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;
    }
}

解密服务

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 私钥解密示例。

控制器

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 / 原生都能直接使用。

安装依赖

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

npm install sm-crypto

混合加密工具

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 处理字符串数据。

页面调用示例

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);
}

四、完整交互过程

前端获取公钥

请求:

get /api/public-key

响应:

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

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

前端组装明文 json

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

前端生成随机 sm4 key

例如:

a8cd3ef7hj2kl9mn

前端加密

  • data = sm4.encrypt(payload, sm4key)
  • key = sm2.doencrypt(sm4key, publickey, 1)

最终请求体:

{
  "key": "sm2加密后的sm4密钥(hex)",
  "data": "sm4加密后的业务json(hex)"
}

后端解密

  • 用 sm2 私钥解出 sm4key
  • 用 sm4 key 解出 plainjson
  • 解析出 username/password/timestamp

五、为什么这样更合理

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

  • sm2 负责保护一个很小的随机密钥
  • sm4 负责高效加密真正的业务数据

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

六、最容易踩的坑

1. 前端公钥格式错

不能把 getpublickeybase64() 直接给前端。前端要的是 04 + x + y 的原始公钥,不是 x.509/asn.1 编码的公钥。hutool 文档明确区分了公钥的 q 值x.509 两种不同格式。

2. sm2 模式不一致

前端这里固定:

const ciphermode = 1;

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

3. sm4 key 长度不对

hutool 文档中自定义 sm4 key 的示例是 128 位,也就是 16 字节。这里前端随机生成 16 个 ascii 字符,后端按 utf-8 读取后恰好是 16 字节。

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

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

5. 仍然必须用 https

这套字段级加密不能替代 tls。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": "登录成功"
}

 以上就是springboot算法实现数据加密传输的详细内容,更多关于springboot数据加密传输的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com