实现Java与前端之间国密算法加解密与签名

createh52个月前 (02-01)技术教程20

问题

开发中发现,我们采用Java或者NodeJS的国密SM2加密、解密、签名单边操作等正常,但是通过Nodejs加密或者生成的签名在Java下就不能解密或者不能验签。整个百度都看了,还是没有搞定通过国密算法实现前端加密、签名Java解密验签。没错,看完下面就全部都将明白。

前端部分

  1. 安装国密操作依赖
npm install --save sm-crypto 
  1. 生成sm2公私钥
// 引入sm-crypto
const sm2 = require("sm-crypto").sm2;
// 生成密钥对
let keypair = sm2.generateKeyPairHex();
// 公钥
publicKey = keypair.publicKey; 
// 私钥
privateKey = keypair.privateKey; 			
  1. 签名

注意:由于要能java解密必须配置参数hash是true,true不是默认值。

// 参数介绍(明文,私钥,配置项)
// 由于要能java解密必须配置参数hash是true,true不是默认值。
sm2.doSignature("asdasd",  privateKey, {
  hash: true
})
  1. 验签

注意:由于要能java解密必须配置参数hash是true,true不是默认值。

// 参数介绍(明文,签名值,公钥,配置项)
sm2.doVerifySignature("asdasd", "4c31b7d83e5ea9a77bb369be9eb28a418aa3f29b83c6df251abab9d38685a0f3e1f54fb667f6b0901915ea7c662ff7bc439b0f575359aa4a4dbc7e849ce40061", publicKey,
{
  hash: true
})
  1. 加密
const msgString = "20201325xjr"

// 加密
sm2.doEncrypt(msgString, publicKey)
  1. 解密
// 解密
sm2.doDecrypt("c278ca67cb5def5b8bed5919d9ce4a8ea29f49d6145d0b6b8acffce81c64de2691d8cf1d888cec20eab11b90c4e3816751afb8e8e42d309decdd81e162085a4c2760ab85c6e938574c005140c4ff6eb57fec03336aef8e179ce41c2119a98809b5a04f40344fa1eadd03d3d6cb1c", privateKey)

后端部分

  1. 引入maven依赖

  org.bouncycastle
  bcprov-jdk15on
  1.70



  cn.hutool
  hutool-all
  5.8.16
  1. 生成sm2公私钥
SM2 sm2 = SmUtil.sm2();
String privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey()));
String publicKey = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false));
System.out.println(privateKey);
System.out.println(publicKey);
  1. 签名

注意:前端采用C1C3C2模式,DSA编码,并且是16进制字符串,所有这些模式需要配置完成。

		/**
     * 签名
     *
     * @param privateKey 私钥
     * @param msg        味精
     * @return {@link String}
     */
public static String signature(String privateKey, String msg) {
  return SmUtil.sm2(privateKey, null).usePlainEncoding().setMode(SM2Engine.Mode.C1C3C2).signHex(HexUtil.encodeHexStr(msg));
}
  1. 验签
 /**
     * 验证签名
     *
     * @param publicKey 公钥
     * @param signHex   十六进制符号
     * @param msg       味精
     * @return {@link Boolean}
     */
    public static Boolean doVerifySignature(String publicKey, String signHex, String msg) {
        return SmUtil.sm2(null, publicKey).setMode(SM2Engine.Mode.C1C3C2).usePlainEncoding().verify(msg.getBytes(), HexUtil.decodeHex(signHex));
    }
  1. 加密

注意:由于Java生成的代码多了04字符串,所以需要截取掉,不然前端没法解密

/**
     * 加密
     *
     * @param publicKey 公钥
     * @param data      明文
     * @return 密文
     */
    public static String encrypt(String publicKey, String data) {
        return SmUtil.sm2(null, publicKey).encryptHex(data.getBytes(), KeyType.PublicKey)
                // 加密后,密文前面会有04,需要去掉
                .substring(2);
    }
  1. 解密

注意:由于前端解密部分少了04字符串,所以解密是否需要添加

/**
     * 解密
     *
     * @param privateKey 私钥
     * @param data       密文
     * @return 明文
     */
    public static String decrypt(String privateKey, String data) {
        // 前端加密是没有04的,所以解析的时候要加04
        data = "04" + data;
        return SmUtil.sm2(privateKey, null).decryptStr(data, KeyType.PrivateKey);
    }

总结

跨语言间不能加解密和验证签名,主要问题还是Nodejs和JAVA在SM2算法实现上默认配置导致的问题。

  1. 前端
// npm install --save sm-crypto 
const sm2 = require("sm-crypto").sm2;

// let keypair = sm2.generateKeyPairHex();

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

publicKey = '04164e5372cf8a44d091780ccacde2ed3b7b1199285d004261a2dc8f9ce893d79001f348cd8feaeeacb883c703bc9dca8f215ba6bd3a20b6169908d3a902e030ad'; // 公钥
privateKey = '7f054f0a7bd3b03199646d7195887ee48dda2ed9eb2b579f4acd13c9fd788872'; // 私钥

// 签名
const sign = sm2.doSignature("asdasd",  privateKey, {
  hash: true
})
console.log(sign)

// 验签
console.log(sm2.doVerifySignature("asdasd", "4c31b7d83e5ea9a77bb369be9eb28a418aa3f29b83c6df251abab9d38685a0f3e1f54fb667f6b0901915ea7c662ff7bc439b0f575359aa4a4dbc7e849ce40061", publicKey,
{
  hash: true
}));

const msgString = "20201325xjr"

// 加密
console.log(sm2.doEncrypt(msgString, publicKey))

// 解密
console.log(sm2.doDecrypt("c278ca67cb5def5b8bed5919d9ce4a8ea29f49d6145d0b6b8acffce81c64de2691d8cf1d888cec20eab11b90c4e3816751afb8e8e42d309decdd81e162085a4c2760ab85c6e938574c005140c4ff6eb57fec03336aef8e179ce41c2119a98809b5a04f40344fa1eadd03d3d6cb1c", privateKey))
  1. 后端
  • 工具类
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import org.bouncycastle.crypto.engines.SM2Engine;

/**
 * 加解密工具类
 *
 * @author yanliang
 */
public class Sm2Util {
    /**
     * 加密
     *
     * @param publicKey 公钥
     * @param data      明文
     * @return 密文
     */
    public static String encrypt(String publicKey, String data) {
        return SmUtil.sm2(null, publicKey).encryptHex(data.getBytes(), KeyType.PublicKey)
                // 加密后,密文前面会有04,需要去掉
                .substring(2);
    }

    /**
     * 解密
     *
     * @param privateKey 私钥
     * @param data       密文
     * @return 明文
     */
    public static String decrypt(String privateKey, String data) {
        // 前端加密是没有04的,所以解析的时候要加04
        data = "04" + data;
        return SmUtil.sm2(privateKey, null).decryptStr(data, KeyType.PrivateKey);
    }

    /**
     * 验证签名
     *
     * @param publicKey 公钥
     * @param signHex   十六进制符号
     * @param msg       味精
     * @return {@link Boolean}
     */
    public static Boolean doVerifySignature(String publicKey, String signHex, String msg) {
        return SmUtil.sm2(null, publicKey).setMode(SM2Engine.Mode.C1C3C2).usePlainEncoding().verify(msg.getBytes(), HexUtil.decodeHex(signHex));
    }


    /**
     * 签名
     *
     * @param privateKey 私钥
     * @param msg        味精
     * @return {@link String}
     */
    public static String signature(String privateKey, String msg) {
        return SmUtil.sm2(privateKey, null).usePlainEncoding().setMode(SM2Engine.Mode.C1C3C2).signHex(HexUtil.encodeHexStr(msg));
    }
}
  • 测试类
 public void test() {
        //// 生成公私钥
        //SM2 sm2 = SmUtil.sm2();
        //String privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey()));
        //String publicKey = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false));
        //System.out.println(privateKey);
        //System.out.println(publicKey);

        String publicKey = "04164e5372cf8a44d091780ccacde2ed3b7b1199285d004261a2dc8f9ce893d79001f348cd8feaeeacb883c703bc9dca8f215ba6bd3a20b6169908d3a902e030ad";
        String privateKey = "7f054f0a7bd3b03199646d7195887ee48dda2ed9eb2b579f4acd13c9fd788872";

        // 解密
        String decrypt = Sm2Util.decrypt(privateKey, "9d15178ae86c5679735dfbd2e556a64e5bcbc3c7ff9117e95ff9c58b81f71219394dd306509a3e1063d9caf0276f4019c77ff66c4be4bcc9b7ef076431adb14254c67a842773819af812f1d4c4a03d4d091f401adcef97b21fa852d4117e067c496b7a84c2de330a97b1ad");
        System.out.println(decrypt);

        // 加密
        String encryData = Sm2Util.encrypt(publicKey, "asdni你啊后");
        System.out.println(encryData);


        // 签名
        String data = "asdasd";
        String sign = Sm2Util.signature(privateKey, data);
        System.out.println(sign);

        // 验签
        sign = "c0899f68fbeaa9569126ec64a9519d0771d2dc26dbb2d1f01d7c4a0ff5a49aa4089302c2604b4ed84c6f8a17ee8fa5efc75ce80e8d959097b79b4861cbbcfc37";
        Boolean b = Sm2Util.doVerifySignature(publicKey, sign, data);
        System.out.println(b);
    }

相关文章

消息认证码、数字签名与数字认证(消息认证码和数字签名的作用)

参考?:学习迪菲-赫尔曼密钥交换的笔记消息认证码例:通过安全的方式,A、B都获得了共享密钥,A想发送一个编号,并获取其代表的实体信息。A将明文编号XYZ加密后传给B。而此时C拦截并伪造了密文,B接到假...

Java中数字签名,非对称加密实现方式

我们在做技术接口时,尤其对外提供时,为了提高服务接口的安全(防爆破,防重放,防篡改等)一般会采用接口验证的方式,但是在验证的时候为了提升参数请求前后的安全,我们会采用加密。普通加密基本都是对称的,不...

如何确保消息只被消费一次:Java实现详解

引言在分布式系统中,消息传递是系统组件间通信的重要方式,而确保消息在传递过程中只被消费一次是一个关键问题。如果一个消息被多次消费,可能会导致业务逻辑重复执行,进而产生数据不一致、错误操作等问题。特别是...

Java程序员学习Typescript初级篇(java程序猿)

引言对于 Java 程序员来说,TypeScript(简称 TS)是一门非常值得学习的语言。TS 是 JavaScript 的一个超集,它在 JavaScript 的基础上添加了静态类型系统,这与 J...

软件申请代码签名证书(代码签名证书时出错30005)

使用代码签名证书为您的可执行文件添加数字签名并消除安全警告。当最终用户下载您的软件时,他们可以确信您的代码是安全的,并且自签名以来从未被篡改过。代码签名证书及其私钥必须存储在符合FIPS标准的硬件安全...

10道超难回答的Java面试题!(附答案)

正文这是我收集的10个最棘手的Java面试问题列表。这些问题主要来自 Java 核心部分 ,不涉及 Java EE 相关问题。你可能知道这些棘手的 Java 问题的答案,或者觉得这些不足以挑战你的 J...