SM2 java+hutool方法示例

讲一下我遇到并解决了的问题(前端版本不动)
  1. 前段VUE写的,加密方法对应的秘钥是16进制格式,后端用椭圆矩阵方法生成一直是base64串
  2. 统一密钥后,前端加密后的串码以base64输出的,解码成16进制再解密一直有问题。同时也尝试了前端用HEX输出同样不行,参考了很多在线的解密,都能解出来就很奇怪
  3. 修改了解密方法后,后端的加密方法也要同时修改

首先先贴一段前端用的加密方法

import {SM2 } from 'gm-crypto'
// SM2加密
export function encrypt(txt) {
    const encryptedData = SM2.encrypt(txt, publicKey, {
        inputEncoding: 'utf8',
        outputEncoding: 'base64' //以base64格式输出
    })
    return encryptedData
}
生成SM2的密钥

前端生成的密钥串是16进制格式的,为了与前端方法对上我参考了下面的方法,这个方法生成的是16进制的密钥,和前端生成的密钥相同。

public static void generateKey() throws NoSuchAlgorithmException {
        // 1. bc库原始写法
        X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
        ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
        ECKeyPairGenerator ecKeyPairGenerator = new ECKeyPairGenerator();
        ecKeyPairGenerator.init(new ECKeyGenerationParameters(ecDomainParameters, SecureRandom.getInstance("SHA1PRNG")));
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = ecKeyPairGenerator.generateKeyPair();

        //16进制格式的私钥,后端使用
        BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
        String privateKeyHex = privatekey.toString(16);

        //16进制格式的公钥,发给前端
        ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
        byte[] encoded = ecPoint.getEncoded(false);
        String publicKeyHex = Hex.toHexString(encoded);

        System.out.println(privateKeyHex);
        System.out.println(publicKeyHex);

        // 2. hutool写法
        SM2 sm2 = SmUtil.sm2();
        String hutoolPrivateKeyHex = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey()));
        String hutoolPublicKeyHex = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false));
        System.out.println(hutoolPrivateKeyHex);
        System.out.println(hutoolPublicKeyHex);
    }
私钥串:00e7e77943b97df66e0426670b40cf0de65336fd2cd1a5ca80581832c4655131a7
公钥串:047472c78673c0786c5c9e4c98af52ff5f662a6aacc1ab9f6a987d7503256e3f9985bee544285e19b5c5689f7e1d1e38b18c4e1c56c96a468c25ad2b37b3b7bf77
公钥串加密

有几个注意点:

  1. 一般的公钥串都是128个字符,分别对应椭圆曲线的x+y,然后这里的公钥串生成的是130,所以需要去掉开始第一个字节,第一个字节表示标记,对应的就是04,我了解到是SM2公钥串都是以04开头的。
  2. 公钥加密的时候创建sm2对象可以单独只给公钥,对应只可以用来加密
  3. 需要设置DSA signatures的编码为PlainDSAEncoding
  4. Mode需要和前端保持一致,BC库给的一般都是C1C2C3,我这里改成C1C3C2
  5. 加密的时候我生成的是hex也就是16进制的明文串,并且去掉了他的前两位字符 ,也就是04,并且转换为base64格式输出,这点是为了和前端加密后输出保持一致(前端VUE加密后的秘文需要在前面加04,否则后端解密会报错,这就是我一直解不出前端给的16进制加密串的原因)
public static String encrypt(String sSrc) throws Exception {
        String publicKey = PUBLICKEY;
        if (publicKey.length() == 130) {
            //这里需要去掉开始第一个字节 第一个字节表示标记
            publicKey = publicKey.substring(2);
        }
        String xhex = publicKey.substring(0, 64);
        String yhex = publicKey.substring(64, 128);
        ECPublicKeyParameters ecPublicKeyParameters = BCUtil.toSm2Params(xhex, yhex);
        //创建sm2 对象
        SM2 sm2 = new SM2(null, ecPublicKeyParameters);
        sm2.usePlainEncoding();
        sm2.setMode(SM2Engine.Mode.C1C3C2);
        String hex = sm2.encryptHex(sSrc,KeyType.PublicKey).substring(2);
        return cn.hutool.core.codec.Base64.encode(HexUtil.decodeHex(hex));
    }
私钥串解密

同样有几个注意点:

  1. 私钥解密的时候创建sm2对象可以单独只给私钥,对应只可以用来解密
  2. 模式要和前端模式相同C1C3C2
  3. 前端vue加密完的密文要加‘04’,否则后端解密16禁止字符串转字节数组的时候会有问题,我把我的16进制转换的也贴出来了
public static String decrypt(String sSrc) throws Exception {
    ECPrivateKeyParameters privateKeyParameters = BCUtil.toSm2Params(PRIVATEKEY);
    //创建sm2 对象
    SM2 sm2 = new SM2(privateKeyParameters, null);
    sm2.usePlainEncoding();
    sm2.setMode(SM2Engine.Mode.C1C3C2);
    return sm2.decryptStr("04"+toHex(Base64.getDecoder().decode(sSrc)), KeyType.PrivateKey);
}

//base64转hex 16进制
private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

private static final String toHex(byte[] data) {
    final StringBuffer sb = new StringBuffer(data.length * 2);
    for (int i = 0; i < data.length; i++) {
        sb.append(DIGITS[(data[i] >>> 4) & 0x0F]);
        sb.append(DIGITS[data[i] & 0x0F]);
    }
    return sb.toString().toLowerCase();
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐