赞
踩
最近因为工作需要,项目中需要采用国密加密,最后根据需求采用了SM2加密。这里简单总结一下用法和踩的坑,供分享和交流。
后端依赖引入(Maven,pom.xml):
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<!-- 上面这个依赖为加密功能必须引入的加密库,习惯称为BC库,
根据自己jdk选取合适的,这里的jdk15on指的是所有大于jdk1.5的版本 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.13</version>
</dependency>
<!-- 上面这个是为了方便使用加密解密引入的hutool工具库 -->
前端依赖引入:
npm install --save sm-crypto
下载报错的话可以用淘宝镜像下载:npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
BC库生成的公钥前面包含有04标志位(解密的时候需要有标志位),似乎BC库生成的私钥前面偶尔会多加两个00(可能也是标志位?),记得去掉00就可以。
除了公钥外,使用BC库/也有可能是Hutool工具库进行sm2加密后的密文,前两位也是04标志位,而其进行解密的时候也必须有该标志位。
在前后端进行sm2加解密交互的时候,需要注意可能需要对标志位进行判断和处理。
前端解密时,出现两个问题:
加密有两种模式可选,C1C2C3和C1C3C2,其决定了加密形式和密文的排布规则。前端sm-crypto库在使用时需要将模式对应的值(1 - C1C3C2,0 - C1C2C3,默认为1,现在主流的加密模式也是C1C3C2)传入,后端BC库默认是按照C1C2C3模式,因此默认情况下前后端是无法正常交互的。
其实C1C2C3模式已经不太常用。好在hutool库做了调整,将加密模式调整为C1C3C2,且提供了很多丰富方便的方法,简化加密解密、密钥生成及转码等过程。
(Maven项目,需要引用的库在上面写了)
package com.test; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.BCUtil; import cn.hutool.crypto.ECKeyUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.SM2; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; /** * @Description: SM2加密的简单DEMO */ public class SM2Demo { // 填写你自己生成的私钥,形如: // 2d06e738e864ba5e384da84eb3156516600b87644a9019600944203d5ad7be23 private static String PRIVATE_KEY = "填写你自己生成的私钥!"; // 填写你自己生成的公钥,形如: // 0457beded4312c0293cb78ec15b0f50ea47c49802e9dcd4547383fd3585b82ca2088bb5fa0796c28542710e20e78291376b0748f08e5eb76c2f6f5119d8ecca225 private static String PUBLIC_KEY = "填写你自己生成的公钥!"; public static void main(String[] args) { getSM2KeyPair(); } /** * 获取一对随机的SM2私钥和公钥,注意公钥是以04标志位开头的 * 这里获取并打印后,将获取到的密钥填入上面的常量里,供后续使用 */ public static void getSM2KeyPair(){ SM2 sm2 = SmUtil.sm2(); byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey()); // 这里选择不压缩,携带标志位04 byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false); //打印当前的公私秘钥,复制下来供之后使用 System.out.println("私钥: " + HexUtil.encodeHexStr(privateKey)); System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey)); } /** * SM2解密 * @param param 密文 * @return 解密后的明文字符串 */ public static String decrypt(String param) throws Exception { SM2 sm2 = SmUtil.sm2(ECKeyUtil.toSm2PrivateParams(PRIVATE_KEY), ECKeyUtil.toSm2PublicParams(PUBLIC_KEY)); String paramBeforeDecrypt; // 使用BC库进行解密时需要在密文前面加标志位04 if (!param.startsWith("04")) { paramBeforeDecrypt = "04" + param; } else { paramBeforeDecrypt = param; } byte[] decryptFromBcd = sm2.decryptFromBcd(paramBeforeDecrypt, KeyType.PrivateKey); if (decryptFromBcd != null && decryptFromBcd.length > 0){ return StrUtil.utf8Str(decryptFromBcd); }else { throw new Exception("解密失败"); } } /** * SM2加密 * @param param 加密前的明文 * @return 加密后的密文 */ public static String encrypt(String param) { SM2 sm2 = SmUtil.sm2(ECKeyUtil.toSm2PrivateParams(PRIVATE_KEY), ECKeyUtil.toSm2PublicParams(PUBLIC_KEY)); String encryptBcd = sm2.encryptBcd(param, KeyType.PublicKey); if (encryptBcd != null ){ // 跟前端(使用sm-crypto库)调试时,前端解密时不能带标志位04,这里做处理去掉密文最前面的04标志位 if (encryptBcd.startsWith("04")){ encryptBcd = encryptBcd.substring(2); } // 前端解密时只能解小写形式的16进制数据(A-F不行,a-f可),这里将所有大写字母转化为小写 encryptBcd = encryptBcd.toLowerCase(); } return encryptBcd; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。