再探国密,SM2和SM4实现的数字信封对接国企
事情起因
事情发生在两天前,一位朋友找到我问我SM2解密报错问题,和上次都是密评,hutool库和纯bc库都无法解密,而且在私钥前面加00
,密文加04
也无法解码.
事情经过
大致上就是解密过程,有两部,外层解密和内层解密,外层是有SM4
解密文件,得到内层文件,如下图
解密过程
大家假如对国密算法(SM)
不是很熟悉,就会去百度上搜索,当然这个问题也没法谷歌,国外用的确实比较少,很多人会遇见SM2
解密时候,
- 私钥加
00
.
因为这个是java
的锅,java
中BigInteger
转换byte[]
占用最低高位来表示符号,所以私钥一单没法用32Byte
表示就要出现33Byte
现象.谨记私钥d
是一个大正整数. - 密文加
0x04
这个是标识,一般会在工具类封装 - 公钥加
0x04
,小部分是0x02
等
主要是因为公钥有很多分类,0x04
代表未压缩的,也就是64Byte
,对接某些C类语言
不用,具体见实现.
但是这个并不是这次解密错误的原因,在使用上述方式后解密过程会报错,Invalid point encoding 0x30
等
具体的原因是因为在SM4.key
这个加密的文件是使用ASN.1
编码的导致需要先把编码后的密文解析为正常的C1C3C2新国标GM/T 0003.4-2012
的密文之后进行解析.其他自行参考.
解密代码参考
import cn.hutool.crypto.BCUtil;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.zz.gmhelper.FileUtil;
import org.zz.gmhelper.SM2Util;
import org.zz.gmhelper.SM4Util;
import java.math.BigInteger;
import java.util.Arrays;
public static void main(String[] args) {
try {
//私钥,这个是d值产生的,本质是一个正整数,下面是假的,换成自己的私钥
String priHex = "8778888XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
//加密后的SM4密钥文件
String keyPath = "/Users/lin/Desktop/gmhelper/gmhelper/src/main/resources/sm4.key";
//最终db文件的数据压缩包
String zipPath = "/Users/lin/Desktop/gmhelper/gmhelper/src/main/resources/xxxxxxxx_xxxxx_xxxxx.zip";
//解密得到的数据文件
String unzipPath = "/Users/lin/Desktop/gmhelper/gmhelper/src/main/resources/1.db";
//获取由d值生成的私钥
ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(
new BigInteger(ByteUtils.fromHexString(priHex)), SM2Util.DOMAIN_PARAMS);
//读取ASN.1 der编码的sm4密钥文件
byte[] fData = FileUtil.readFile(keyPath);
//读取sm4加密的db文件
byte[] dbData = FileUtil.readFile(zipPath);
//解密der编码密文到C1C3C2
byte[] r = SM2Util.decodeDERSM2Cipher(fData);
//解密
byte[] decryptedData = SM2Util.decrypt(priKey, r);
String sm4Key = new String(decryptedData).substring(0,16);
//解密db文件
byte[] dd = SM4Util.decrypt_ECB_NoPadding(sm4Key.getBytes(),dbData);
FileUtil.writeFile(unzipPath,dd);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
public class FileUtil {
public static void writeFile(String filePath, byte[] data) throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) {
raf.write(data);
}
}
public static byte[] readFile(String filePath) throws IOException {
byte[] data;
try (RandomAccessFile raf = new RandomAccessFile(filePath, "r")) {
data = new byte[(int) raf.length()];
raf.read(data);
return data;
}
}
加密过程
相对于解密过程,加密过程就比较轻松了,但是请注意,加密之后的密文有一个04
,请手动去除,之后进行ASN.1
编码
加密过程参考代码
//导入的包
import cn.hutool.crypto.BCUtil;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.zz.gmhelper.FileUtil;
import org.zz.gmhelper.SM2Util;
import org.zz.gmhelper.SM4Util;
import java.math.BigInteger;
import java.util.Arrays;
public static void getEncodeSM4Key(byte[] cipher){
try {
//公钥,不加04
String pubHex = "0D1762507F6BD5152545DE6F771396A9FA40958743DD75FE7FB71FBA3D56D045C595C8011E316E0A43CAFFA6FB5C9E2DE97F2EEF8289404EEA7CAA6E484BD4AB";
//生成的加密sm4文件路径
String keyPath = "G:\\code\\project\\Java\\gmhelper\\gmhelper\\src\\main\\resources\\sm4der.key";
String pubX = pubHex.substring(0,64);
String pubY = pubHex.substring(64,128);
//生成公钥,BC库封装的工具类
ECPublicKeyParameters ecPublicKeyParameters = BCUtil.toSm2Params(pubX,pubY);
//正常加密C1C3C2
byte[] sm4eKey = SM2Util.encrypt(ecPublicKeyParameters,cipher);
byte[] sm4dKey = new byte[sm4eKey.length-1];
//去掉加密后的首位04,注意
System.arraycopy(sm4eKey, 1, sm4dKey, 0, sm4dKey.length);
//编码后的der
byte[] sm4DerKey = SM2Util.encodeSM2CipherToDER(sm4dKey);
//保存为文件
FileUtil.writeFile(keyPath,sm4DerKey);
System.out.println(Arrays.toString(FileUtil.readFile(keyPath)));
}catch (Exception e){
System.out.println(e.getMessage());
}
工具类参考https://github.com/ZZMarquis/gmhelper
文档参考 http://www.gmbz.org.cn/main/bzlb.html