【开发随记】国密SM2的简单运用和踩坑

最近因为工作需要,项目中需要采用国密加密,最后根据需求采用了SM2加密。这里简单总结一下用法和踩的坑,供分享和交流。

1.依赖引入

后端依赖引入(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

2. 密钥和密文的标志位问题

BC库生成的公钥前面包含有04标志位(解密的时候需要有标志位),似乎BC库生成的私钥前面偶尔会多加两个00(可能也是标志位?),记得去掉00就可以。
除了公钥外,使用BC库/也有可能是Hutool工具库进行sm2加密后的密文,前两位也是04标志位,而其进行解密的时候也必须有该标志位。
在前后端进行sm2加解密交互的时候,需要注意可能需要对标志位进行判断和处理。

3. 前端解密问题

前端解密时,出现两个问题:

  1. 加密的结果以及解密时的密文都不能含有04标志位,且公钥也不能含有04标志位;
  2. 加密的结果是小写的16进制数的字符串即含a-f不含A-F,解密时密文也必须是小写形式的,如果是大写的会解密失败,暂未研究从根本上的解决办法,采用处理方式为前端解密前或者后端传给前端密文前将大写字符全部转换为小写字符。

4. 其他问题

加密有两种模式可选,C1C2C3和C1C3C2,其决定了加密形式和密文的排布规则。前端sm-crypto库在使用时需要将模式对应的值(1 - C1C3C2,0 - C1C2C3,默认为1,现在主流的加密模式也是C1C3C2)传入,后端BC库默认是按照C1C2C3模式,因此默认情况下前后端是无法正常交互的。
其实C1C2C3模式已经不太常用。好在hutool库做了调整,将加密模式调整为C1C3C2,且提供了很多丰富方便的方法,简化加密解密、密钥生成及转码等过程。

5. 后端SM2简单Demo

(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;
    }
}
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值