事情起因
事情發生在兩天前,一位朋友找到我問我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