0.背景
1.格式
在准则中可以看到,签名计算的最后一步是将两个数字转换为字符串。
SM2签名的长度为128位(R+S = 64+64 = 128),有时候我们看到的不止128位,多半是因为做了ASN1格式转换。
2.分析
下方以软加密和加密机签名的结果做分析:
// 软加密签名结果142:
3045022100d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a602201b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715
// 加密机签名结果128:
d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a61b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715
可以看到,上方代码计算结果长度为142,加密机为128。
咦,连长度都对不上。
这里提前给出结果,下方将描述如何转换:
// 142位的是ASN1的格式
3045022100d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a602201b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715
// 128位的是字符串格式
d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a61b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715
2.1 ASN1实体类编写
由于SM2的签名结果是由两个数字拼接构成,构建时我们使用ASN1Integer。
import cn.yang37.utils.YangHexUtils;
import org.bouncycastle.asn1.*;
import java.math.BigInteger;
/**
* @description: SM2签名 ASN1格式
* @class: SM2SignAsn1
* @author: yang37
* @date: 2022/2/8 9:47
* @version: 1.0
*/
public class SM2SignAsn1 extends ASN1Object {
private ASN1Integer int1;
private ASN1Integer int2;
public ASN1Integer getInt1() {
return int1;
}
public void setInt1(ASN1Integer int1) {
this.int1 = int1;
}
public ASN1Integer getInt2() {
return int2;
}
public void setInt2(ASN1Integer int2) {
this.int2 = int2;
}
public SM2SignAsn1(ASN1Integer int1, ASN1Integer int2) {
this.int1 = int1;
this.int2 = int2;
}
@Override
public ASN1Primitive toASN1Primitive() {
ASN1EncodableVector vector = new ASN1EncodableVector();
vector.add(int1);
vector.add(int2);
return new DERSequence(vector);
}
/**
* 构建ASN1对象
*/
public static String buildSm2SignAsn1Object(BigInteger int1, BigInteger int2) {
String res = null;
try {
SM2SignAsn1 sm2SignAsn1 = new SM2SignAsn1(
new ASN1Integer(int1),
new ASN1Integer(int2)
);
byte[] encoded = sm2SignAsn1.getEncoded();
res = YangHexUtils.byteArrToHex(encoded);
} catch (Exception e) {
e.printStackTrace();
}
return res;
}
}
2.2 转换为ASN1格式
对于数据
d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a61b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715
由128拆分为两个64
d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a6
1b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715
上方16进制数转换为大整数,带入buildSm2SignAsn1Object方法即可。
import org.junit.jupiter.api.Test;
import java.math.BigInteger;
class SM2SignAsn1Test {
/**
* 构建ASN1
*/
@Test
void test1(){
// hex构建
BigInteger bigInteger1 = new BigInteger("EC0E1276175276F2F82046DFB26D06125E1B3F23981AF78C14F6DCDE4ADB19C4", 16);
BigInteger bigInteger2 = new BigInteger("10589A985509C217E9B691F258B9FAB074DBDEE60AACDF4052950D499619E33E", 16);
String sm2SignAsn1 = SM2SignAsn1.buildSm2SignAsn1Object(bigInteger1, bigInteger2);
System.out.printf("ASN1格式: %s",sm2SignAsn1);
System.out.println();
System.out.printf("长度: %d",sm2SignAsn1.length());
}
}
输出:
ASN1格式: 3045022100d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a602201b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715
长度: 142
2.3 解析ASN1格式数据
上方的ASN1格式数据也可以解析回去,下面是一个demo:
注意40行,这里是因为SM2签名是两个整数组成,即我们开始用的ASN1Integer在构建,所以这里把结果数据做了16进制转换。
不是所有的ASN1都应当这样操作,要结合实体类分析,即2.2节的SM2SignAsn1。
又比如SM2数字信封是4部分组成,看我的这个问题:SM2加密结果转ASN1格式时如何构造DerOctetString?
import org.bouncycastle.asn1.*;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
/**
* @description:
* @class: YangASN1Utils
* @author: yang37
* @date: 2022/2/8 9:59
* @version: 1.0
*/
public class YangASN1Utils {
private static final Logger log = LoggerFactory.getLogger(YangASN1Utils.class);
/**
* 解析SM2的ASN1格式签名数据
*
* @param asn1Base64 base64
* @return 大整数 R+S
*/
public static String parseSm2SignAsn1Object(String asn1Base64) {
StringBuilder sb = new StringBuilder();
byte[] data = Base64.decode(asn1Base64);
ASN1InputStream ais = new ASN1InputStream(data);
ASN1Primitive primitive;
try {
while ((primitive = ais.readObject()) != null) {
// 原数据
log.info("解析到sequence:\n{}", primitive);
if (primitive instanceof ASN1Sequence) {
ASN1Sequence sequence = (ASN1Sequence) primitive;
ASN1SequenceParser parser = sequence.parser();
ASN1Encodable encodable;
while ((encodable = parser.readObject()) != null) {
primitive = encodable.toASN1Primitive();
String temp = String.valueOf(primitive);
//String s = new BigInteger(temp).toString(16);
String s = String.format("%064x",new BigInteger(temp))
log.info("结果数据: {}", s);
sb.append(s);
}
}
}
return sb.toString().replace("#", "");
} catch (Exception e) {
log.error("解析ASN1格式的SM2签名失败!", e);
return "";
}
}
}
解析数据:
/**
* 解析ASN1
*/
@Test
void test2() {
String signBase64 = YangHexUtils.hexToBase64("3045022100ec0e1276175276f2f82046dfb26d06125e1b3f23981af78c14f6dcde4adb19c4022010589a985509c217e9b691f258b9fab074dbdee60aacdf4052950d499619e33e");
String s = YangASN1Utils.parseSm2SignAsn1Object(signBase64);
System.out.printf("解析ASN1结果: %s", s);
}
结果:
2022-02-08 10:13:40.028 -- [main] INFO cn.yang37.utils.YangASN1Utils.parseSm2SignAsn1Object - 解析到sequence:
[96609110044744864114854453461814827124752609040423567819705864411478992913318, 12471937173666031536193805568391885468913788814372661482682467986500971300629]
2022-02-08 10:13:40.032 -- [main] INFO cn.yang37.utils.YangASN1Utils.parseSm2SignAsn1Object - 结果数据: d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a6
2022-02-08 10:13:40.033 -- [main] INFO cn.yang37.utils.YangASN1Utils.parseSm2SignAsn1Object - 结果数据: 1b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715
解析ASN1结果: d596d18be77035b0bb9ef6abf232e9e81f2df3178bedd56d64220dc72c6883a61b92ddc4c167e22956e5ef32ce19bf4c05f9d6d96aa82c41ace0ba28acba8715