Spring Boot中使用Bouncy Castle实现SM2国密算法(与前端JS加密交互)

Spring Boot中使用Bouncy Castle实现SM2国密算法(与前端JS加密交互)

    • 一、环境准备
    • 二、核心实现
    • 三、前后端交互流程
    • 四、关键问题解决方案
    • 五、常见问题排查
    • 六、最佳实践建议

在这里插入图片描述

在现代Web应用中,数据安全传输至关重要。SM2作为我国自主设计的非对称加密算法,在安全性、效率和合规性方面具有显著优势。本文将详细介绍如何在Spring Boot中集成SM2算法,实现与前端JS的无缝加密交互。

一、环境准备

技术栈:

  • Java 1.8
  • Spring Boot 2.1.18
  • Bouncy Castle 1.68+
  • 前端:sm-crypto或类似库

Maven核心依赖:

<dependencies><dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.68</version></dependency>
</dependencies>

二、核心实现

  1. 密钥生成服务
@RestController
@RequestMapping("/sm2")
public class SM2Controller {@GetMapping("/keypair")public Map<String, String> generateKeyPair() throws Exception {KeyPair keyPair = SM2CryptoUtil.generateKeyPair();ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();String publicKeyHex = Hex.toHexString(publicKey.getQ().getEncoded(false));String privateKeyHex = privateKey.getD().toString(16);// 标准化私钥格式(64字符)privateKeyHex = String.format("%64s", privateKeyHex).replace(' ', '0');return Map.of("publicKey", publicKeyHex,  // 130字符带04前缀"privateKey", privateKeyHex  // 64字符);}
}
  1. SM2解密服务
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.encoders.Hex;import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;/*** @author cmamg* @title: Base64Util* @projectName * @description: TODO* @date 2025/7/29*/
public class SM2CryptoUtil {// 加密模式常量public static final int C1C2C3 = 0;public static final int C1C3C2 = 1;// 椭圆曲线参数private static final X9ECParameters EC_PARAMS;private static final ECDomainParameters DOMAIN_PARAMS;private static final BigInteger CURVE_ORDER;static {Security.addProvider(new BouncyCastleProvider());EC_PARAMS = GMNamedCurves.getByName("sm2p256v1");DOMAIN_PARAMS = new ECDomainParameters(EC_PARAMS.getCurve(),EC_PARAMS.getG(),EC_PARAMS.getN(),EC_PARAMS.getH());CURVE_ORDER = EC_PARAMS.getN();}/*** 生成SM2密钥对*/public static KeyPair generateKeyPair() throws Exception {ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");kpg.initialize(spec, new SecureRandom());return kpg.generateKeyPair();}/*** 获取压缩公钥十六进制字符串*/public static String getCompressedPublicKey(ECPoint publicKey) {byte[] compressed = publicKey.getEncoded(true);return Hex.toHexString(compressed);}/*** 获取未压缩公钥十六进制字符串(不带04前缀)*/public static String getUncompressedPublicKey(ECPoint publicKey) {byte[] uncompressed = publicKey.getEncoded(false);// 去掉开头的04标识return Hex.toHexString(uncompressed);}/*** 从十六进制字符串解析公钥*/public static ECPoint parsePublicKey(String publicKeyHex) {// 添加04前缀表示未压缩格式byte[] pubKeyBytes = Hex.decode( publicKeyHex);return DOMAIN_PARAMS.getCurve().decodePoint(pubKeyBytes);}private static BigInteger parsePrivateKey(String privateKeyHex) {if (privateKeyHex == null || privateKeyHex.length() != 64) {throw new IllegalArgumentException("私钥必须是64字符十六进制字符串");}try {BigInteger privateKey = new BigInteger(privateKeyHex, 16);// 验证私钥范围 [1, n-1]if (privateKey.signum() <= 0 || privateKey.compareTo(CURVE_ORDER) >= 0) {throw new IllegalArgumentException("私钥超出有效范围");}return privateKey;} catch (NumberFormatException e) {throw new IllegalArgumentException("无效的私钥格式", e);}}public static String decryptStr(String ciphertextHex, String privateKeyHex) throws Exception {return new String(decrypt(ciphertextHex,privateKeyHex, 1), "UTF-8");}/*** SM2解密*/public static byte[] decrypt(String ciphertextHex, String privateKeyHex, int cipherMode) throws Exception {// 1. 验证并解析私钥BigInteger privateKey = parsePrivateKey(privateKeyHex);// 2. 解析密文byte[] ciphertext = Hex.decode(ciphertextHex);// 验证最小长度 = C1(64) + C3(32) = 96字节if (ciphertext.length < 96) {throw new IllegalArgumentException("密文太短");}// 3. 拆分密文byte[] c1 = Arrays.copyOfRange(ciphertext, 0, 64); // 64字节byte[] c3;byte[] c2;if (cipherMode == C1C2C3) {// C1C2C3模式: C1(64) + C2 + C3(32)c3 = Arrays.copyOfRange(ciphertext, ciphertext.length - 32, ciphertext.length);c2 = Arrays.copyOfRange(ciphertext, 64, ciphertext.length - 32);} else {// C1C3C2模式: C1(64) + C3(32) + C2c3 = Arrays.copyOfRange(ciphertext, 64, 96);c2 = Arrays.copyOfRange(ciphertext, 96, ciphertext.length);}// 4. 重建C1点byte[] c1Full = new byte[65]; // 04 + 64字节c1Full[0] = 0x04; // 添加未压缩标识System.arraycopy(c1, 0, c1Full, 1, 64);ECPoint c1Point;try {c1Point = DOMAIN_PARAMS.getCurve().decodePoint(c1Full);} catch (Exception e) {throw new IllegalArgumentException("无效的C1点", e);}// 5. 计算共享点 (x2, y2) = privateKey * C1ECPoint s = c1Point.multiply(privateKey).normalize();// 验证点是否在曲线上if (!s.isValid()) {throw new SecurityException("计算出的点不在曲线上");}byte[] x2 = BigIntegers.asUnsignedByteArray(32, s.getXCoord().toBigInteger());byte[] y2 = BigIntegers.asUnsignedByteArray(32, s.getYCoord().toBigInteger());// 6. KDF生成密钥流byte[] z = new byte[x2.length + y2.length];System.arraycopy(x2, 0, z, 0, x2.length);System.arraycopy(y2, 0, z, x2.length, y2.length);byte[] t = kdf(z, c2.length);// 7. 异或解密byte[] msg = new byte[c2.length];for (int i = 0; i < c2.length; i++) {msg[i] = (byte) (c2[i] ^ t[i]);}// 8. 验证C3byte[] u = new byte[x2.length + msg.length + y2.length];System.arraycopy(x2, 0, u, 0, x2.length);System.arraycopy(msg, 0, u, x2.length, msg.length);System.arraycopy(y2, 0, u, x2.length + msg.length, y2.length);byte[] calculatedC3 = sm3(u);if (!Arrays.equals(c3, calculatedC3)) {throw new SecurityException("C3验证失败: 数据可能被篡改或密钥错误");}return msg;}/*** KDF密钥派生函数*/private static byte[] kdf(byte[] z, int keylen) {int ct = 1;int offset = 0;byte[] result = new byte[keylen];SM3Digest digest = new SM3Digest();while (offset < keylen) {// 准备计数器字节byte[] ctBytes = new byte[]{(byte) (ct >>> 24),(byte) (ct >>> 16),(byte) (ct >>> 8),(byte) ct};// 计算SM3哈希digest.update(z, 0, z.length);digest.update(ctBytes, 0, 4);byte[] hash = new byte[digest.getDigestSize()];digest.doFinal(hash, 0);// 填充结果int copyLen = Math.min(keylen - offset, hash.length);System.arraycopy(hash, 0, result, offset, copyLen);offset += copyLen;ct++;digest.reset();}return result;}/*** SM3哈希计算*/private static byte[] sm3(byte[] input) {SM3Digest digest = new SM3Digest();digest.update(input, 0, input.length);byte[] hash = new byte[digest.getDigestSize()];digest.doFinal(hash, 0);return hash;}
}
  1. 前端加密示例
import { sm2 } from 'sm-crypto';// 使用后端生成的公钥(130字符带04前缀)
const publicKey = '04d4de...'; function encryptMessage(message) {// 使用C1C3C2模式加密const ciphertext = sm2.doEncrypt(message, publicKey, 1 // cipherMode=1 表示C1C3C2);return ciphertext; // 十六进制字符串
}// 调用示例
const encrypted = encryptMessage('敏感数据123');

三、前后端交互流程

密钥获取:

GET /sm2/keypair
Response: { "publicKey": "04...", "privateKey": "a1b2..." }

前端加密:

const ciphertext = sm2.doEncrypt(data, publicKey, 1);

后端解密:

POST /sm2/decrypt
{"ciphertext": "a1b2c3...","privateKey": "a1b2...","mode": 1
}

四、关键问题解决方案

  1. 公钥格式一致性
    前端要求公钥带04前缀(未压缩格式),后端需确保:
public String getPublicKeyHex(ECPoint publicKey) {return Hex.toHexString(publicKey.getEncoded(false)); // 带04前缀
}
  1. 私钥范围验证
    防止Scalar not in interval错误:
private static final BigInteger CURVE_ORDER = EC_PARAMS.getN();if (privateKey.signum() <= 0 || privateKey.compareTo(CURVE_ORDER) >= 0) {throw new IllegalArgumentException("无效私钥范围");
}
  1. C1点重建
    前端密文中的C1点不带04前缀,后端需重建:
byte[] c1Full = new byte[65];
c1Full[0] = 0x04; // 添加前缀
System.arraycopy(c1, 0, c1Full, 1, 64);

五、常见问题排查

错误现象 可能原因 解决方案
Scalar not in interval 私钥格式错误或越界 验证私钥长度64字符,值在[1, n-1]范围内
C3验证失败 密钥错误或数据篡改 检查公私钥配对,重试加密流程
无效的C1点 密文格式错误 确认使用C1C3C2模式,检查密文长度
解密乱码 编码不一致 统一使用UTF-8编码

六、最佳实践建议

密钥管理:

前端不存储私钥

后端使用HSM或KMS管理私钥

定期轮换密钥

性能优化:

// 重用SM3Digest实例
private static final ThreadLocal<SM3Digest> sm3Cache = ThreadLocal.withInitial(SM3Digest::new);

安全增强:

// 防止时序攻击
if (!MessageDigest.isEqual(c3, calculatedC3)) {throw new SecurityException("C3验证失败");
}

七、总结
本文实现了Spring Boot中完整的SM2算法集成方案,重点解决了:

密钥生成与格式标准化

与前端JS的加密交互

解密过程中的异常处理

通过此方案,开发者可以快速构建符合国密标准的安全应用,确保数据传输的机密性和完整性。在实际业务中,建议结合HTTPS等传输层安全措施,构建纵深防御体系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/917114.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/917114.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习sklearn:随机森林的决策树

bg&#xff1a;对比决策树来说&#xff0c;搞多几棵树就是随机森林了rlf_1 [] rlf_2 [] for i in range(10):rfc RandomForestClassifier(n_estimators25)rfc_s cross_val_score(rfc, wine.data, wine.target, cv10).mean()rlf_1.append(rfc_s)clf DecisionTreeClassifier…

上海月赛kk

1.十六进制#include<bits/stdc.h> using namespace std;int n;int main(){cin>>n;stack<int>re;if(n<16)cout<<0;while(n){re.push(n%16);n/16;}while(!re.empty()){int xre.top();re.pop();if(x<10)cout<<x;else cout<<char(Ax-10)…

暑期算法训练.12

目录 52. 力扣1 两数之和 52.1 题目解析&#xff1a; 52.2 算法思路&#xff1a; 52.3 代码演示&#xff1a; ​编辑 52.4 总结反思&#xff1a; 53 面试题&#xff1a;判定是否互为字符重排 53.1 题目解析&#xff1a; 53.2 算法思路&#xff1a; 53.3 代码演示&…

MySQL时间处理完全指南:从存储到查询优化

时间是数据库中最活跃的数据维度之一&#xff0c;正确处理时间数据关系到系统稳定性、数据分析准确性和业务逻辑正确性。本文将深入剖析MySQL时间处理的完整知识体系。一、MySQL时间数据类型详解1. 核心时间类型对比类型存储空间范围特性时区影响DATE3字节1000-01-01~9999-12-3…

Text2SQL 智能问答系统开发-预定义模板(二)

背景 在构建一个支持多轮对话的 Text2SQL 系统过程中&#xff0c;我完成了以下关键功能&#xff1a; 已完成 基础 Text2SQL 功能实现 实现用户输入自然语言问题后&#xff0c;系统能够自动生成 SQL 并执行返回结果。用户交互优化 支持用户通过补充信息对查询进行调整&#xff0…

JavaScript 异步编程:Promise 与 async/await 详解

一、Promise 1. 什么是 Promise&#xff1f; Promise 是 JavaScript 中用于处理异步操作的对象&#xff0c;它代表一个异步操作的最终完成&#xff08;或失败&#xff09;及其结果值。 2. Promise 的三种状态 ​​Pending&#xff08;待定&#xff09;​​&#xff1a;初始状态…

OS架构整理

OS架构整理引导启动部分bios bootloader区别启动流程&#xff08;x86 BIOS 启动&#xff09;&#xff1a;biosboot_loader3.切换进保护模式实模式的限制如何切换进保护模式加载kernel到内存地址1M加载内核映像文件elf一些基础知识链接脚本与代码数据段创建GDT表段页式内存管理显…

【WRF-Chem第二期】WRF-Chem有关 namelist 详解

目录namelist 选项&#xff1a;chem_opt 的选择其他化学相关的 namelist 选项气溶胶光学属性与输出边界与初始条件配置&#xff08;气体&#xff09;参考本博客详细介绍 WRF-Chem有关 namelist 选项。 namelist 选项&#xff1a;chem_opt 的选择 chem_opt 是什么&#xff1f;…

STM32-USART串口实现接收数据三种方法(1.根据\r\n标志符、2.空闲帧中断、3.根据定时器辅助接收)

本章概述思维导图&#xff1a;USART串口初始化配置串口初始化配置在&#xff08;STM32-USART串口初始化章节有详细教程配置&#xff09;&#xff0c;本章不做讲解直接代码示例&#xff0c;本章重点在于串口实现接收数据三种方法&#xff1b;配置USART1串口接收初始化函数步骤&a…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博评论数据可视化分析-点赞区间折线图实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解微博评论数据可视化分析-点赞区间折线图实现…

Unity_SRP Batcher

SRP Batcher 全面解析&#xff1a;原理、启用、优化与调试一、什么是 SRP Batcher&#xff1f;SRP Batcher 是 Unity Scriptable Render Pipeline&#xff08;URP、HDRP 或自定义 SRP&#xff09; 专属的 CPU 渲染性能优化技术&#xff0c;核心目标是 减少材质切换时的 CPU 开销…

详解Vite 配置中的代理功能

在前端开发过程中&#xff0c;你可能经常会遇到一个头疼的问题&#xff1a;当你在本地启动的前端项目中调用后端接口时&#xff0c;浏览器控制台会报出类似 “Access to fetch at ‘http://xxx’ from origin ‘http://localhost:3000’ has been blocked by CORS policy” 的错…

理解梯度在神经网络中的应用

梯度&#xff08;Gradient&#xff09;是微积分中的一个重要概念&#xff0c;广泛应用于机器学习和深度学习中&#xff0c;尤其是在神经网络的训练过程中。下面将从梯度的基本概念、其在神经网络中的应用两个方面进行详细介绍。一、梯度的基本概念 1.1 什么是梯度&#xff1f; …

WPF,按钮透明背景实现MouseEnter

在帮手程序&#xff08;assister.exe&#xff09;中&#xff0c;可以点击录制按钮&#xff0c;实现录制用户操作直接生成操作列表。而在弹出录制按钮的悬浮窗中&#xff0c;需要能够拖动录制按钮放置在任意的位置&#xff0c;以免阻挡正常的窗口。具体功能是&#xff0c;当鼠标…

【抄袭】思科交换机DAI(动态ARP监控)配置测试

一.概述 1.DAI作用 ①.使用DAI&#xff0c;管理员可以指定交换机的端口为信任和非信任端口&#xff1a; 信任端口可以转发任何ARP信息 非信任端口的ARP消息要进行ARP检测验证 ②.交换机执行如下的ARP验证&#xff1a; 静态ARP监控&#xff1a;为一个静态的IP地址配置一个静态AR…

在嵌入式系统或 STM32 平台中常见的外设芯片和接口

在嵌入式系统或 STM32 平台中常见的 外设芯片 或 模块名称&#xff0c;包括&#xff1a; &#x1f4fa; 显示驱动&#xff08;如 ST7735、OTM8009A、NT35510&#xff09;&#x1f4f7; 摄像头模组&#xff08;如 OV5640、OV9655、S5K5CAG&#xff09;&#x1f4be; Flash 存储器…

AI 类型的 IDE

指集成了 AI 辅助编程能力的集成开发环境 一、代码辅助生成 ✅ 自动补全&#xff08;更智能&#xff09; 比传统 IDE 更智能&#xff0c;理解上下文&#xff0c;生成整个函数/模块 示例&#xff1a;根据函数名 calculateTax 自动生成税务计算逻辑 ✅ 函数 / 类自动生成 给…

JP3-3-MyClub后台后端(一)

Java道经 - 项目 - MyClub - 后台后端&#xff08;一&#xff09; 传送门&#xff1a;JP3-1-MyClub项目简介 传送门&#xff1a;JP3-2-MyClub公共服务 传送门&#xff1a;JP3-3-MyClub后台后端&#xff08;一&#xff09; 传送门&#xff1a;JP3-3-MyClub后台后端&#xff08;…

架构实战——互联网架构模板(“存储层”技术)

目录 一、SQL 二、NoSQL 三、小文件存储 四、大文件存储 本文来源:极客时间vip课程笔记 一、SQL SQL 即我们通常所说的关系数据。前几年 NoSQL 火了一阵子,很多人都理解为 NoSQL 是完全抛弃关系数据,全部采用非关系型数据。但经过几年的试验后,大家发现关系数据不可能完全被…

CentOS7.9在线部署Dify

一、CentOS7.9安装dify 二、检查是否安装dcoker docker --version2.1下载后将安装包上传至服务器对应文件夹下,我选在放在了 /root文件夹下 cd /root2.2 上传至服务器 cd /root #对应目录下tar -xvf docker-26.1.4.tgz # 解压安装包:chmod 755 -R docker # 赋予可执…