SM2 java+hutool方法示例
讲一下我遇到并解决了的问题(前端版本不动)
- 前段VUE写的,加密方法对应的秘钥是16进制格式,后端用椭圆矩阵方法生成一直是base64串
- 统一密钥后,前端加密后的串码以base64输出的,解码成16进制再解密一直有问题。同时也尝试了前端用HEX输出同样不行,参考了很多在线的解密,都能解出来就很奇怪
- 修改了解密方法后,后端的加密方法也要同时修改
首先先贴一段前端用的加密方法
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
公钥串加密
有几个注意点:
- 一般的公钥串都是128个字符,分别对应椭圆曲线的x+y,然后这里的公钥串生成的是130,所以需要去掉开始第一个字节,第一个字节表示标记,对应的就是04,我了解到是SM2公钥串都是以04开头的。
- 公钥加密的时候创建sm2对象可以单独只给公钥,对应只可以用来加密
- 需要设置DSA signatures的编码为PlainDSAEncoding
- Mode需要和前端保持一致,BC库给的一般都是C1C2C3,我这里改成C1C3C2
- 加密的时候我生成的是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));
}
私钥串解密
同样有几个注意点:
- 私钥解密的时候创建sm2对象可以单独只给私钥,对应只可以用来解密
- 模式要和前端模式相同C1C3C2
- 前端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();
}