Vue+Springboot前后端完整使用国密算法SM2双公私钥对数据加密传输交互完整解决方案

Vue+Springboot 前后端完整使⽤国密算法SM2双公私钥对数据加密传输交互完整解决⽅案项⽬,特别是企事业单位的项⽬,第三方测试公司做安全测试时,常常要求使用国密算法,因涉及服务端和客户端的交互,传递关键数据时要求使用SM2非对称加密。

引入相关依赖

这里我使用的是jdk1.8 的maven项目,需要在pom.xml里引入以下依赖:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.22</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.14</version>
</dependency>

 可复用代码

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.asymmetric.SM2;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class SM2Utils {

    static final BouncyCastleProvider bc = new BouncyCastleProvider();

    public static Map<String,Object> generateKey(){
        Map<String,Object> map = new HashMap<>();
        SM2 sm2=new SM2();

        BCECPrivateKey privateKey = (BCECPrivateKey) (sm2.getPrivateKey());
        BigInteger d = privateKey.getD();

        BCECPublicKey publicKey = (BCECPublicKey) sm2.getPublicKey();
        ECPoint q = publicKey.getQ();

        String hutoolPrivateKeyHex = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(privateKey));
        String hutoolPublicKeyHex = HexUtil.encodeHexStr(q.getEncoded(false));

        map.put("publicKeyHex",hutoolPublicKeyHex);
        map.put("privateKeyHex",hutoolPrivateKeyHex);
        return map;
    }
}

服务端在登录校验成功后生成服务端公私钥对,并把改公私钥对存放到session中,便于下次会话提取,同时将公钥字符串返回给客户端: 

//生成服务器公私钥对,并将服务器公私钥放入session
Map<String,Object> mapKey = SM2Utils.generateKey();
session.setAttribute("publicKeyHex",mapKey.get("publicKeyHex").toString());
session.setAttribute("privateKeyHex",mapKey.get("privateKeyHex").toString());  
//公钥和正常消息一起返回给客户端
map.put("publicKeyHex", mapKey.get("publicKeyHex").toString());

客户端收到服务端给出的公钥后,存入localStorage,并生成客户端公私钥对,存入localStorage,并用接收到的服务端公钥加密客户端公私钥对,将加密后的密文发给服务端

npm install --save sm-crypto

const sm2 = require('sm-crypto').sm2
//生成客户端公私钥
const { publicKey, privateKey } = sm2.generateKeyPairHex()
window.localStorage.setItem("publicKey", publicKey)
window.localStorage.setItem("privateKey", privateKey)

//获取服务器端公钥
let serverPublicKey = res.spk
window.localStorage.setItem("serverPublicKey", serverPublicKey)

//加密客户端公私钥

encryptpublickeyClient = sm2.doEncrypt(publicKey, serverPublicKey,cipherMode);
encryptpublickeyClient = '04' + encryptpublickeyClient;
encryptprivatekeyClient = sm2.doEncrypt(privateKey, serverPublicKey,cipherMode);
encryptprivatekeyClient = '04' + encryptprivatekeyClient;

 服务端在二次接收客户端请求时,从参数中获取加密后的客户端公私钥对密文,使用服务端秘钥解密后,将客户端公私钥对存入会话,并在服务器生成随机数,用客户端公钥加密后返回数据给前端:

String publicKeyHex = session.getAttribute("publicKeyHex").toString();
String privateKeyHex = session.getAttribute("privateKeyHex").toString();
//privateKey 为上述生成的私钥 publicKey为生成的公钥,注意 此处不是Q值
SM2 sm2 = SmUtil.sm2(privateKeyHex, publicKeyHex);
//body为加密后的数据(注意:此处加密数据可能缺少04开头,解密会失败,需要手动在body前拼上04,body="04"+body)
String clientPublicKeyStr = sm2.decryptStr(userDTO.getClientPublicKey(), KeyType.PrivateKey);
String clientPrivateKeyStr = sm2.decryptStr(userDTO.getClientPrivateKey(), KeyType.PrivateKey);
SM2 sm2Client = SmUtil.sm2(clientPrivateKeyStr, clientPublicKeyStr);
String uid = UUID.randomUUID().toString().replaceAll("-","");
String uidEncode = sm2Client.encryptHex(uid,KeyType.PublicKey);
map.put("uid", uidEncode);

客户端接收到服务端的uid密文后,使用客户端私钥解密,并存入localStorage中

let uidEcrpyptCode = res.uid;
let headerStr = uidEcrpyptCode.substring(0, 2)                 

if(headerStr == '04'){
       uidEcrpyptCode = uidEcrpyptCode.substring(2, uidEcrpyptCode.length)
 }

//解密uid
let uidStr = sm2.doDecrypt(uidEcrpyptCode, '00'+privateKey)
window.localStorage.setItem("uid", uidStr);

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值