SM2实现前端发送加密报文,后端解密报文

Posted Xfei0314

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SM2实现前端发送加密报文,后端解密报文相关的知识,希望对你有一定的参考价值。

原作者在这 https://www.cnblogs.com/iroc1994/p/16399496.html感谢原创
此文做记录使用,同时稍作修改,思路正确,内容未核验,有问题请不吝指正,后期会验证不断修改!
环境:

在工作中,前端页面发送的报文可能涉及到用户信息,为确保数据安全,需要对请求的数据加密,采用SM2非对称加密,可以有效解决数据的安全问题。

前端加密,后端解密Demo源码下载地址

https://gitee.com/iroc-git/springboot-encryptreq.git

实现步骤:

第一步:在Maven项目中引入pom依赖,注意依赖的jar包版本要在1.6以上,同时检查是否有bcprov-jdk16.*.的jar包(一般都会有),需要删掉(不放心可以先备份),否则会冲突报错

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

第二步:生成公钥和私钥

工具类GetPKs

package com.iroc.springboot.util;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.security.SecureRandom;

//获取sm2公钥与私钥
public class GetPKs

    public static void main(String[] args)
    
        try
        
            X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
            ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
            ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
            keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
            AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();

            //上面的代码都是直接用maven依赖中的包直接import就可以用了
            //还有一些更底层的写法,可以自己搜索一下,图方便的这个挺好的

            //私钥,16进制格式,自己保存
            BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
            String privateKeyHex = privatekey.toString(16);
            System.out.println("private Key :" + privateKeyHex);

            //公钥,16进制格式,发给前端
            ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
            String publicKeyHex = Hex.toHexString(ecPoint.getEncoded(false));
            System.out.println("Public Key :" + publicKeyHex);
         catch (Exception e)
        
            e.printStackTrace();
        
    


执行后:


private Key :5e3dcfb19758d7829ebce1ee4276f8193951e0cb77040a319403fce5231cab0e
Public Key :045417fe9233682d37355d2f64db75fb6ff1146efb68cf409cdd64bb7e31a448047c337b0edd919501f678181a230d91e621029bc64faf7fca3631ac2c869673b9

第三步:在前端页面中对请求数据加密

1.引入crypto-js.js和sm2.js

请在github中下载(https://github.com/Saberization/SM2)
下载这两个文件放到项目对应的位置
2.对请求报文加密

//sm2公钥
var pubkeyHex = "045417fe9233682d37355d2f64db75fb6ff1146efb68cf409cdd64bb7e31a448047c337b0edd919501f678181a230d91e621029bc64faf7fca3631ac2c869673b9";
//对请求报文加密
jsonReqParams=sm2Encrypt(jsonReqParams, pubkeyHex, 0);

前端页面login.html完整的代码

<!DOCTYPE html>

<head>
    <meta charset="utf-8" />
    <script src="../js/crypto-js.js"></script>
    <script src="../js/sm2.js"></script>
    <script src="../js/jquery-3.4.1.min.js"></script>
    <script>

        $(document).ready(function () 
            $("#submit").click(function () 
                var username = $("#username").val();
                var password = $("#password").val();
                var jsonReqParams = 

                ;
                jsonReqParams.username=username;
                jsonReqParams.password=password;
                jsonReqParams=JSON.stringify(jsonReqParams);
                //sm2公钥
                var pubkeyHex = "045417fe9233682d37355d2f64db75fb6ff1146efb68cf409cdd64bb7e31a448047c337b0edd919501f678181a230d91e621029bc64faf7fca3631ac2c869673b9";
                //对请求报文加密
                jsonReqParams=sm2Encrypt(jsonReqParams, pubkeyHex, 0);
                //发送请求到后端
                $.ajax(
                    type: 'POST', //方法类型
                    dataType: 'json', //预期服务器返回的数据类型
                    url: '/login', //请求地址
                    data:  'jsonReqParams': jsonReqParams ,
                    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
                    success: function (data) 
                        if(data.code == "1001")
                            $("#result").html("登录成功");
                        else if(data.code== "1002")
                            $("#result").html("登录失败");
                        else
                            $("#result").html("出现异常,请联系管理员");
                        
                    ,
                    error: function () 
                        $("#result").html("出现异常,请联系管理员");
                    ,
                );
            );
        );

    </script>
</head>

<body>

    用户名:<input type="text" id="username" /><br />
    密码:<input type="password" id="password" /><br />
    <button id="submit">登录</button><br />
    <span id="result"></span>

</body>

第四步:后端接收前端的请求报文,并解密

1.在后端引入解密工具类DecryptUtil.java

package com.iroc.springboot.util;

import org.apache.tomcat.util.codec.binary.Base64;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;

//解密前端页面的请求报文
public class DecryptUtil

    public static String decrypt(String cipherData) throws Exception
        byte[] cipherDataByte = Hex.decode(cipherData);

        //sm2私钥
        String privateKey = "5e3dcfb19758d7829ebce1ee4276f8193951e0cb77040a319403fce5231cab0e";
        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        X9ECParameters sm2ECParameters1 = GMNamedCurves.getByName("sm2p256v1");
        ECDomainParameters domainParameters1 = new ECDomainParameters(sm2ECParameters1.getCurve(), sm2ECParameters1.getG(), sm2ECParameters1.getN());
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters1);

        //用私钥解密
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, privateKeyParameters);

        //processBlock得到Base64格式,记得解码
        byte[] arrayOfBytes = Base64.decodeBase64(sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length));

        //得到明文:SM2 Encryption Test
        String data = new String(arrayOfBytes);
        return data;
    

2.解密前端请求的报文

package com.iroc.springboot.controller;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.iroc.springboot.util.DecryptUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class LoginController


    @RequestMapping(value = "/login",method = RequestMethod.POST)
    @ResponseBody
    public String login(@RequestParam(value = "jsonReqParams") String jsonReqParams)
        JsonObject result = new JsonObject();
        //设数据库中username为zhangsan,password为123456
        try
        
            //解密前端请求的报文
            jsonReqParams = DecryptUtil.decrypt(jsonReqParams);
            JsonObject jsonObject = new JsonParser().parse(jsonReqParams).getAsJsonObject();
            String username = jsonObject.get("username").getAsString();
            String password = jsonObject.get("password").getAsString();
            if("zhangsan".equals(username)&&"123456".equals(password))
                result.addProperty("code","1001");
                result.addProperty("desc","登录成功");
            else 
                result.addProperty("code","1002");
                result.addProperty("desc","账号或密码有误");
            
        catch (Exception e)
            e.printStackTrace();
            result.addProperty("code","1003");
            result.addProperty("desc","出现异常,请联系管理员");
        

        return result.toString();
    


参考文章

  1. https://blog.csdn.net/python_small_pan/article/details/120064529

  2. https://github.com/Saberization/SM2

  3. https://blog.51cto.com/boytnt/2503384

国密SM2前端VUE,后端Hutool工具的搭配

背景

当要实现请求参数加密的功能,使用的是国密SM2算法,前端向后台发送请求获取公钥,将请求加密发送到后台,后台用对应的私钥进行解密

解决方案:

前端采用:SM2加密js库(sm-crypto)
后端采用:Hutool工具

由于当前的前端SM2加密js库(sm-crypto)都是使用SM2公钥的Q值转成16进制进行加密,所以在后台给前端发送公钥时,需要提取公钥的Q值并且转成16进制。

后端生成秘钥对与前端的公钥公钥Q

/**
     * 生成SM2公钥,私钥,公钥Q,私钥D <br/>
     * 通常由于js端提供的 SM2代码实现的方案,都是直接使用的私钥的d值和公钥的q值直接进行的加解密,
     * 所以后端返回的是从公钥里面提取的q值,以q值做为js端的加密公钥,从私钥提取d值,作为js端的解密私钥
     * @author 杨攀
     * @date 2021/5/13 16:29
     * @param
     * @return void
     */
    public static void createSm2Key() {

        SM2 sm = SmUtil.sm2();

        // sm2的加解密时有两种方式即 C1C2C3、 C1C3C2,
        sm.setMode(SM2Engine.Mode.C1C3C2);

        // 生成私钥
        String privateKey = HexUtil.encodeHexStr(sm.getPrivateKey().getEncoded());
        LOGGER.info("私钥: {}", privateKey);
        // 生成公钥
        String publicKey = HexUtil.encodeHexStr(sm.getPublicKey().getEncoded());
        LOGGER.info("公钥: {}", publicKey);

        // 生成私钥D
        String privateKeyD = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm.getPrivateKey())); // ((BCECPrivateKey) privateKey).getD().toByteArray();
        LOGGER.info("私钥D: {}", privateKeyD);

        // 生成公钥Q,以q值做为js端的加密公钥
        String publicKeyQ = HexUtil.encodeHexStr(((BCECPublicKey) sm.getPublicKey()).getQ().getEncoded(false));
        LOGGER.info("公钥Q: {}", publicKeyQ);
    }

前段根据公钥Q进行加密

前端测试代码:

data() {
            return {
                // 请求的 service
                service: this.$svc.sys.demo,
                // 表单数据
                formData: {
                    content: \'\',
                    encContent: \'\',
                    decContent: \'\',
                },
                // 公钥Q
                publicKey:\'04be3415fd3a7231fa23e4cfdf6f857b0f3137e75692f7b5011d459afc0cdd7741676dca32ca6489cfe0f0fd43b5e9f9f0f77c7997630ba1142c725178a9181558\',
                // 私钥D (测试才放前端)
                privateKey: \'74b24bfa8a55127c0fcbe87d7a112e398fd291fd72314e1c56e8d575664a1148\',
            }
        },

方法:

// 加密
            sm2Encrypt() {

                const sm2 = require(\'sm-crypto\').sm2

                //let keypair = sm2.generateKeyPairHex()

                //this.publicKey = keypair.publicKey // 公钥
                //this.privateKey = keypair.privateKey // 私钥

                const cipherMode = 1  // 1 - C1C3C2,0 - C1C2C3,默认为 1

                //let publicKey = "3059301306072a8648ce3d020106082a811ccf5501822d03420004efddac6873492c460eac87b0b12bec1258f181a5093d62bbb622a7192d58c6c7278f45880dfc226bb0b645268e33a3a8de9d12b7397e05ab729fb35ead70dded";

                let encContent = sm2.doEncrypt(this.formData.content, this.publicKey, cipherMode) // sm2加密
                this.formData.encContent = encContent;
            },
            // 解密
            sm2Decrypt() {
                const sm2 = require(\'sm-crypto\').sm2
                let encryptData = this.formData.encContent;
                const cipherMode = 1  // 1 - C1C3C2,0 - C1C2C3,默认为 1

                let decryptData = sm2.doDecrypt(encryptData, this.privateKey, cipherMode) // 解密结果sm2

                this.formData.decContent = decryptData;
            },

以上是关于SM2实现前端发送加密报文,后端解密报文的主要内容,如果未能解决你的问题,请参考以下文章

国密SM2的前端加密,后端解密(Java版本)及SM3 摘要加密

JS实现国密算法SM2加密,后端Java解密

thinkphp前端和后端怎么通信

ltsdangerous加密解密

前后端交互数据加解密

加密和解密基础