当前位置:首页 » 《关注互联网》 » 正文

国密SM2 后端Hutool+前端sm-crypto

18 人参与  2024年04月28日 08:03  分类 : 《关注互联网》  评论

点击全文阅读


前言

在网上找了很多文章,都只是单独说了用后端加解密及前端加解密,很少把两个结合起来一起说的文章,本文分享两者结合起来的用法和踩坑。

准备工作

前端项目引入第三方包

npm install --save sm-crypto

后端项目引入工具类

<dependency>    <groupId>cn.hutool</groupId>    <artifactId>hutool-all</artifactId>    <version>5.8.16</version></dependency><dependency>  <groupId>org.bouncycastle</groupId>  <artifactId>bcprov-jdk15to18</artifactId>  <version>1.69</version></dependency>

生成密钥

使用工具类生成两套公私钥: 服务端公私钥、客户端公私钥
前端拿服务端公钥+客户端私钥
后端拿客户端公钥+服务端私钥

本文仅做演示,如何安全存储密钥请自行研究。

import cn.hutool.core.util.HexUtil;import cn.hutool.crypto.BCUtil;import cn.hutool.crypto.SmUtil;import cn.hutool.crypto.asymmetric.SM2;import org.bouncycastle.crypto.engines.SM2Engine;import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;//创建sm2 对象SM2 sm = SmUtil.sm2();        // sm2的加解密时有两种方式即 C1C2C3、 C1C3C2,sm.setMode(SM2Engine.Mode.C1C3C2);        // 生成私钥String privateKey = HexUtil.encodeHexStr(sm.getPrivateKey().getEncoded());log.info("私钥: {}", privateKey);        // 生成公钥String publicKey = HexUtil.encodeHexStr(sm.getPublicKey().getEncoded());log.info("公钥: {}", publicKey); // 生成私钥 D,以D值做为js端的解密私钥     String privateKeyD = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm.getPrivateKey()));     log.info("私钥D: {}", privateKeyD);     // 生成公钥 Q,以Q值做为js端的加密公钥String publicKeyQ = HexUtil.encodeHexStr(((BCECPublicKey) sm.getPublicKey()).getQ().getEncoded(false));log.info("公钥Q: {}", publicKeyQ);
私 钥: 308193020100301306072a8648ce3d020106082a811ccf5501822d047930770201010420b1933b8a0c16c1bcf64dcbcc2cb822c27452dfb8a19b6167e5ee9bec963b8171a00a06082a811ccf5501822da14403420004d6fd83141fe38cee09f8f4e20a72d3ba056dcfeeda4238dc671a1f11e811293c10b8537ea0adb4b9ad1fa37d147b65c451fe4ab51054d71c810cc0e84cd8232d公 钥: 3059301306072a8648ce3d020106082a811ccf5501822d03420004d6fd83141fe38cee09f8f4e20a72d3ba056dcfeeda4238dc671a1f11e811293c10b8537ea0adb4b9ad1fa37d147b65c451fe4ab51054d71c810cc0e84cd8232d私钥D: 00b1933b8a0c16c1bcf64dcbcc2cb822c27452dfb8a19b6167e5ee9bec963b8171公钥Q: 04d6fd83141fe38cee09f8f4e20a72d3ba056dcfeeda4238dc671a1f11e811293c10b8537ea0adb4b9ad1fa37d147b65c451fe4ab51054d71c810cc0e84cd8232d

上面密钥仅供参考

加密及解密

1.前端加密+后端解密

在需要加解密的js文件中引入sm2方法

const sm2 = require('sm-crypto').sm2// 1 - C1C3C2,0 - C1C2C3,默认为1const cipherMode = 1 //服务端公钥 Qconst publicKeyQ ="服务端公钥 Q";//要加密的报文const body={"age":10,"name":"张三"} // 加密let encryStr=sm2.doEncrypt(JSON.stringify(body), publicKeyQ, cipherMode);
 // 加密结果encryStr: aaaba1cea1af9a9c621d75bacc7434e1eea713861885a28ab8b3a5ebc911bf971c76af94acff2ab0647b8e157e9ccb6f78e85d5173120fbb11f9be610c0194c3c7d97b069478c4f07d06982b4c691d530bb817f490f531b271ee8d52bb9babbe8305ff92782f4fe263a6ea63d010c4bb682241aff20b5c11442a

注意:通过前端加密的密文没有04前缀,所以如果我们想要在后端解密需要手动加上

把密文传给后端进行解密

import cn.hutool.core.util.StrUtil;//服务端私钥  String privateKey = "服务端私钥";    //后端解密时,公钥可以不填    String publicKey = "";        //手动加上前缀04String encryStr = "04" + "aaaba1cea1af9a9c621d75bacc7434e1eea713861885a28ab8b3a5ebc911bf971c76af94acff2ab0647b8e157e9ccb6f78e85d5173120fbb11f9be610c0194c3c7d97b069478c4f07d06982b4c691d530bb817f490f531b271ee8d52bb9babbe8305ff92782f4fe263a6ea63d010c4bb682241aff20b5c11442a";SM2 sm2 = SmUtil.sm2(HexUtil.decodeHex(privateKey), null);log.info("密文: {}", encryStr);byte[] decrypt = sm2.decrypt(encryStr, KeyType.PrivateKey);log.info("解密结果: {}", StrUtil.utf8Str(decrypt));
密文: 04aaaba1cea1af9a9c621d75bacc7434e1eea713861885a28ab8b3a5ebc911bf971c76af94acff2ab0647b8e157e9ccb6f78e85d5173120fbb11f9be610c0194c3c7d97b069478c4f07d06982b4c691d530bb817f490f531b271ee8d52bb9babbe8305ff92782f4fe263a6ea63d010c4bb682241aff20b5c11442a解密结果: {"age":10,"name":"张三"}

2.后端加密+前端解密

//后端加密时,可以不填私钥。 客户端公钥SM2 sm2 = SmUtil.sm2(null, HexUtil.decodeHex(publicKey));String str = "Hello World";byte[] encrypt = sm2.encrypt(str, KeyType.PublicKey);String encryStr = HexUtil.encodeHexStr(encrypt);log.info("密文: {}", encryStr);
密文: 046415900ab4af09490d257cdd5aee2d0710b1e222d4386124d6e70f4e39468d2e68d7eb9830db46804b472747e42819bdd56312982da1f765a963cd4c7c51795bd8c3faa220547033740dcb2f91b17039a05c9c6081bbf9c756b90f9a09cfa8cb39e0853766394661b265c60b

注意:后端密文前缀存在04,前端在解密时会在首部自动补充 04,所以我们需截取后再传入。

  const sm2 = require('sm-crypto').sm2  // 1 - C1C3C2,0 - C1C2C3,默认为1  const cipherMode = 1   //客户端私钥 D  let privateKeyD="客户端私钥 D"    //去掉04前缀后的密文  let encryStr ="6415900ab4af09490d257cdd5aee2d0710b1e222d4386124d6e70f4e39468d2e68d7eb9830db46804b472747e42819bdd56312982da1f765a963cd4c7c51795bd8c3faa220547033740dcb2f91b17039a05c9c6081bbf9c756b90f9a09cfa8cb39e0853766394661b265c60b"  const str = sm2.doDecrypt(encryStr , privateKeyD, cipherMode);  console.log("解密报文:",str);  
解密报文: Hello World

签名及验签

1.后端加签+前端验签

  // 服务端私钥 ,签名时可以不填公钥  SM2 sm2 = SmUtil.sm2(HexUtil.decodeHex(privateKey),null);    //要签名的明文数据  String str = "Hello World";  //把明文字符串转为十六进制字符串  String sign = sm2.signHex(HexUtil.encodeHexStr(str));  log.info("签名:{}", sign);
签名:304502207b8a07c36a97c34278bf4352af13f4a1022f79816820c52a1a23d7499e24a609022100dcf53edf3f2d7f648fea027cacce90f6d101923b1215b2cb9aa6811064ed6cdd

前端接受密文和签名

/** * msg  明文数据即解密后数据 * sigValueHex 后端传过来的签名(不需要进行任何处理) * publicKeyQ 服务端公钥 Q * 最后的json对象必传,否则验签失败 */sm2.doVerifySignature(msg, sigValueHex, publicKeyQ ,{der: true,hash: true,}) // 验签结果const sm2 = require('sm-crypto').sm2let signResult = sm2.doVerifySignature(      "Hello World", "304502207b8a07c36a97c34278bf4352af13f4a1022f79816820c52a1a23d7499e24a609022100dcf53edf3f2d7f648fea027cacce90f6d101923b1215b2cb9aa6811064ed6cdd",      publicKeyQ,      { hash: true, der: true }    );console.log("验签结果:", signResult);
验签结果: true

2.前端加签+后端验签

/** * msg  明文数据 * privateKeyD 客户端私钥 D * 最后的json对象必传,否则验签失败 */sm2.doSignature(msg, privateKeyD,{der: true,hash: true,}) // 签名let sign = sm2.doSignature("Hello World",privateKeyD,{der:true,hash:true,});console.log("签名:", sign);
签名:30450220740a651a3f44b7647c884540b19571131da65c88e2750fd8c780d1062af16f9b022100f320694a65f1e85b0320789b9bf9b78c4cfc94f6bc6562e61072389f944ca3de
// 客户端公钥 ,验签时可以不填私钥SM2 sm2 = SmUtil.sm2(null, HexUtil.decodeHex(publicKey));//解密后的报文String str = "Hello World";String sign = "30450220740a651a3f44b7647c884540b19571131da65c88e2750fd8c780d1062af16f9b022100f320694a65f1e85b0320789b9bf9b78c4cfc94f6bc6562e61072389f944ca3de";boolean signResult = sm2.verifyHex(HexUtil.encodeHexStr(str), sign);log.info("验签结果:{}",signResult);
验签结果: true

点击全文阅读


本文链接:http://zhangshiyu.com/post/101352.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1