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