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

SpringBoot+vue集成sm国密加密解密

文章目录

  • 前言
  • 认识SM2
  • 后端工具类实现
    • 引入依赖
    • 代码实现
      • 工具类:SM2Util
    • 单元测试
      • 案例1:生成服务端公钥、私钥,前端js公钥、私钥
      • 案例2:客户端加密,服务端完成解密
      • 案例3:服务端进行加密(可用于后面前端测试解密操作)
  • 前端vue2实现
    • 工具类构建:sm2.js
    • 页面vue测试
  • 常见报错
    • 1、前端加密出现:TypeError: Cannot read properties of null (reading ‘multiply
    • 2、后端解密出现:InvalidCipherTextException: invalid cipher text
  • 参考文章

前言

本章配套代码:Gitee仓库/demo-exer

  • 说明:前端vue工具类和库在resources目录下。

本章节实现思路:后端基于Hutool开源工具提供的SmUtil来完成国密加解密,前端使用sm-crypto来实现加解密。

后端:

  • 开源 sm工具类库:国密算法工具-SmUtil

前端:

  • 开源库:sm-crypto

认识SM2

认识

SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥密码算法。

SM2算法和RSA算法都是公钥密码算法,SM2算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换RSA算法。

随着密码技术和计算机技术的发展,目前常用的1024位RSA算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。

对比RSA

SM2性能更优更安全:密码复杂度高、处理速度快、机器性能消耗更小
详细参考: https://www.ecaa.org.cn/667.html

SM2RSA
算法结构基本椭圆曲线(ECC)基于特殊的可逆模幂运算
计算复杂度完全指数级亚指数级
存储空间192-256bit2048-4096bit
秘钥生成速度较RSA算法快百倍以上
解密加密速度较快一般
加密长度限制117

后端工具类实现

引入依赖

<!--    引入Hutool依赖    -->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-crypto</artifactId><version>5.8.20</version>
</dependency>
<!--    引入Bouncy Castle依赖    -->
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk18on</artifactId><version>1.78.1</version>
</dependency>

代码实现

工具类:SM2Util

image-20240916214852887.

package com.changlu.springboot.sm.util;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.springframework.util.StringUtils;import java.security.KeyPair;public class SM2Util {public static KeyPair generateKeyPair() {return SecureUtil.generateKeyPair("SM2");}/*** 加密* @param str* @return*/public static String encrypt(String str, String privateKey, String publicKey) {try {if (checkKeyIsEmpty(privateKey, publicKey)) {SM2 sm2 = new SM2(privateKey, publicKey);sm2.setMode(SM2Engine.Mode.C1C3C2);return sm2.encryptHex(str, KeyType.PublicKey);}return str;} catch (Exception e) {throw new RuntimeException("sm2加密失败" + e);}}/*** 解密* @param str 加密之后的字符串* @param privateKey 私钥* @param publicKey 公钥* @return 原文密码*/public static String decrypt(String str, String privateKey, String publicKey) {try {if (checkKeyIsEmpty(privateKey, publicKey)) {SM2 sm2 = new SM2(privateKey, publicKey);sm2.setMode(SM2Engine.Mode.C1C3C2);return sm2.decryptStr(str, KeyType.PrivateKey);}return str;} catch (Exception e) {throw new RuntimeException("sm2解密失败" + e);}}private static boolean checkKeyIsEmpty(String privateKey, String publicKey) {if (StringUtils.isEmpty(privateKey) || StringUtils.isEmpty(publicKey)) {return false;}return true;}}

单元测试

image-20240916214837775

案例1:生成服务端公钥、私钥,前端js公钥、私钥

package com.changlu.springboot.sm;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import com.changlu.springboot.sm.util.SM2Util;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.junit.jupiter.api.Test;import java.security.KeyPair;public class SMUtilTest {// 案例1:生成服务端公钥、私钥,前端js公钥、私钥@Testpublic void testDiySM() {String text = "我是一段测试aaaa";// 生成密钥对KeyPair keyPair = SM2Util.generateKeyPair();// 服务器端使用// 生成私钥String privateKey = HexUtil.encodeHexStr(keyPair.getPrivate().getEncoded());// 生成公钥String publicKey = HexUtil.encodeHexStr(keyPair.getPublic().getEncoded());System.out.println("privateKey=>" + privateKey);System.out.println("publicKey=>" + publicKey);// 前端使用// 生成公钥 Q,以Q值做为js端的加密公钥String publicKeyQ = HexUtil.encodeHexStr(((BCECPublicKey) keyPair.getPublic()).getQ().getEncoded(false));System.out.println("公钥Q:"+ publicKeyQ);// 生成私钥 D,以D值做为js端的解密私钥String privateKeyD = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(keyPair.getPrivate()));System.out.println("私钥D:"+ privateKeyD);// 服务端加解密String encodeStr = SM2Util.encrypt(text, privateKey, publicKey);String formatStr = SM2Util.decrypt(encodeStr, privateKey, publicKey);System.out.println("encodeStr=>" + encodeStr);System.out.println("formatStr=>" + formatStr);}}

效果:该单元测试得到的服务端公钥、私钥,前端js公钥、私钥,可用于服务器端、前端使用。

image-20240916214342674


案例2:客户端加密,服务端完成解密

场景:前端客户端使用前端公钥加密后(可用vue中加密函数生成的加密内容),我们在服务器端进行解密。

package com.changlu.springboot.sm;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import com.changlu.springboot.sm.util.SM2Util;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.junit.jupiter.api.Test;import java.security.KeyPair;public class SMUtilTest {// 生成的一组私钥、公钥进行测试private String privateKey = "308193020100301306072a8648ce3d020106082a811ccf5501822d047930770201010420057ab3e1e512e970023c16c545289ecf37dd2cb202daa24c42936f21daa061aca00a06082a811ccf5501822da14403420004981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67";private String publicKey = "3059301306072a8648ce3d020106082a811ccf5501822d03420004981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67";private String publicKeyQ = "04981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67";private String privateKeyD = "057ab3e1e512e970023c16c545289ecf37dd2cb202daa24c42936f21daa061ac";// 案例2:客户端加密,服务端完成解密@Testpublic void testDecrypt() {String encodeStr = "04badafbddce5f728fb11c2007f2230b2fcd0ecf019ac4536370c75dc2e222ca696d20033ab8f76965bd1a9e2691b7a6e4e62d71627874cedd6138453444e1868881e69dbcd3ca13818d6db061561fb87da14e061d9d1c82d550322b2e04c60bcca7998ac51059";String formatStr = SM2Util.decrypt(encodeStr, privateKey, publicKey);System.out.println("formatStr=>" + formatStr);}
}

image-20240916214659774


案例3:服务端进行加密(可用于后面前端测试解密操作)

package com.changlu.springboot.sm;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import com.changlu.springboot.sm.util.SM2Util;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.junit.jupiter.api.Test;
import java.security.KeyPair;public class SMUtilTest {// 生成的一组私钥、公钥进行测试private String privateKey = "308193020100301306072a8648ce3d020106082a811ccf5501822d047930770201010420057ab3e1e512e970023c16c545289ecf37dd2cb202daa24c42936f21daa061aca00a06082a811ccf5501822da14403420004981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67";private String publicKey = "3059301306072a8648ce3d020106082a811ccf5501822d03420004981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67";private String publicKeyQ = "04981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67";private String privateKeyD = "057ab3e1e512e970023c16c545289ecf37dd2cb202daa24c42936f21daa061ac";// 案例3:服务端进行加密@Testpublic void testEncrypt() {String str = "changlu test test";String encodeStr = SM2Util.encrypt(str, privateKey, publicKey);System.out.println("encodeStr=>" + encodeStr);}}

image-20240916214800810


前端vue2实现

工具类构建:sm2.js

导入依赖:

cnpm i sm-crypto --save

工具类utils目录中创建sm2.js:

image-20240916214949913

import { sm2 } from 'sm-crypto';// 公钥
const PUBLIC_KEY = '04981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67'
const PRIVATE_KEY = '057ab3e1e512e970023c16c545289ecf37dd2cb202daa24c42936f21daa061ac'// 可配置参数
// 1 - C1C3C2;	0 - C1C2C3;  默认为1
const cipherMode = 1//加密
export function doSM2Encrypt(str) {let msg = strif (typeof str !== 'string') {msg = JSON.stringify(str)}// console.log(msg,'加密前')let publicKey = PUBLIC_KEY// 加密结果let encryptData = sm2.doEncrypt(msg, publicKey, cipherMode)//Base64编码 自行选择是否使用//let baseEncode = Base64.encode(encryptData)// 加密后的密文前需要添加04,后端才能正常解密 (不添加04,后端处理也可以)let encrypt = '04' + encryptDatareturn encrypt
}// 解密
export function doSM2DecryptStr(enStr) {let msg = enStrif (typeof enStr !== 'string') {msg = JSON.stringify(enStr)}let privateKey = PRIVATE_KEYlet enval = enStr.substring(2)// 解密结果let doDecrypt = sm2.doDecrypt(enval , privateKey, cipherMode)console.log("doDecrypt=>", doDecrypt)// 解密后类型转换return doDecrypt;
}

页面vue测试

任意找一个vue页面来进行测试。

首先引入工具类:

import { doSM2Encrypt, doSM2DecryptStr } from '@/utils/sm2'

编写测试函数:

export default {created() {//测试编码this.testEncode()},methods: {testEncode() {// 案例1:前端进行加密let str = "123456"let encodeStr = doSM2Encrypt(str)console.log("前端明文加密之后=>", encodeStr)// 案例2:前端自己加密之后的内容进行解密let originStr = doSM2DecryptStr(encodeStr)console.log("前端自行加密,解密之后=>", originStr)// 案例3:服务器端内容加密后进行解密let testEncodeStr = '04c719fa9dff41a22b8119a3f1ba984303d19c295f1f6a6b8196c7a330cf0e9e1830cbd3cd949c49be0681fd2fa9abca3ed14f8f8d4111c552ef7603793c0a2ae344e9072b7dcaefaf5785634a624d4ca7addcf4ab9ff37abe4ec69847ee5e24a65ce74e8a5b1aecdc467d18b36fba22a8e8'originStr = doSM2DecryptStr(testEncodeStr)console.log("服务器端进行加密,解密之后=>", originStr)},
}

image-20240916220322600


常见报错

1、前端加密出现:TypeError: Cannot read properties of null (reading ‘multiply

出现异常:

image-20240916172117381

解决方式:

错误:一开始直接将服务端生成的公钥作为前端的公钥,该公钥的前缀没有04,此时就会报错。

image-20240916181159848

3059301306072a8648ce3d020106082a811ccf5501822d03420004981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67

正确方式:

image-20240916181217277

此时得到的公钥为:

04981070e26f624917f2717bcaadc000c928c91b49c9c218df33260cafa1d2243c2427fd3486884a67d390751ff4956e35466fb4b925a666229b22d36c26267d67

2、后端解密出现:InvalidCipherTextException: invalid cipher text

问题描述:

image-20240916171945532

解决方案:

和问题1一致,一开始前端使用的公钥并不是D值作为js端的解密私钥,此时生成出来的自然在解密端无法解除。


参考文章

[1]. 使用sm2出现报错 “TypeError: Cannot read properties of null (reading ‘multiply‘)”:https://blog.csdn.net/ciwei0605/article/details/125844154

[2]. BC库实现SM2解密时InvalidCipherTextException:https://blog.csdn.net/weixin_43504369/article/details/132739118

[3]. 从零玩转前后端加解密之SM2-sm2:https://www.cnblogs.com/yby6/p/17414766.html

[4]. 适用于前后端的SM2国密加密解密:https://blog.csdn.net/weixin_53021967/article/details/131594733

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • AI学习指南深度学习篇-RMSprop的数学原理
  • 【mechine learning-十-梯度下降-学习率】
  • 微软九月补丁星期二发现了 79 个漏洞
  • k8s 资源管理
  • Git常用命令(记录)
  • 怎么浏览URL的PDF文件呢
  • ip映射域名,一般用于mysql和redis的固定映射,方便快捷打包
  • Python实现 Socket.IO 的在线游戏场景
  • Oracle临时表
  • Android自动化1️⃣环境搭建【基于Appium】-基于python
  • Redis搭建集群
  • Leetcode 每日一题:Longest Increasing Path in a Matrix
  • 中医笔记目录
  • 面试经典150题——最后一个单词的长度
  • 学习大数据DAY58 增量抽取数据表
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 【Under-the-hood-ReactJS-Part0】React源码解读
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • Docker下部署自己的LNMP工作环境
  • github指令
  • Intervention/image 图片处理扩展包的安装和使用
  • Java多态
  • js操作时间(持续更新)
  • js递归,无限分级树形折叠菜单
  • js面向对象
  • Object.assign方法不能实现深复制
  • PHP CLI应用的调试原理
  • scala基础语法(二)
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 从零开始在ubuntu上搭建node开发环境
  • 关于字符编码你应该知道的事情
  • 记一次和乔布斯合作最难忘的经历
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 如何实现 font-size 的响应式
  • 深度学习在携程攻略社区的应用
  • 一些css基础学习笔记
  • 优化 Vue 项目编译文件大小
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • #考研#计算机文化知识1(局域网及网络互联)
  • (06)Hive——正则表达式
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (30)数组元素和与数字和的绝对差
  • (70min)字节暑假实习二面(已挂)
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • (JS基础)String 类型
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (六)c52学习之旅-独立按键
  • (一)UDP基本编程步骤
  • (译) 函数式 JS #1:简介