网联回执报文签名验证和敏感信息解密报错:failed to construct sequence from byte[]: corrupted stream detected [国密SM2]
验签问题场景参数:
使用BC包1.57版本将网联签名值格式: ASN.1(R,S)转加密机签名值格式:RS 过程报错
网联签名值格式: ASN.1(R,S)
加密机签名值格式:RS
转换解析过程: org.bouncycastle.asn1.ASN1Sequence.getInstance(网联签名值)
BC包版本:1.57
异常信息:java.lang.IllegalArgumentException: failed to construct sequence from byte[]: corrupted stream detected
异常情况: 随机出现
问题原因
使用BC包1.57版本将网联签名值格式: ASN.1(R,S)转加密机签名值格式:RS 过程报错,推断BC包1.57版本在解释网联签名值的ASN1数据格式有问题
解决方案:
根据网联签名算法的签名值格式来解析读取R值和S值
- SM2签名结果
签名格式为TLV嵌套格式,签名的主体分为R和S两部分。R(或S)的长度等于ECC私钥长度。R(或S)前的T为0x02,签名T为0x30。总体格式如下:
- 30 + LEN1 + 02 + LEN2 + 00 (optional) + r + 02 + LEN3 + 00(optional) + s
当r或s的第1字节大于0x80时,需要在r或s前加1字节0x00。 - LEN3为,0x00(optional) + s 的字节长度。
- LEN2为,0x00(optional) + r 的字节长度。
- LEN1为,LEN2+LEN3+4字节长度。
示例签名值:
** 1.57未报错签名: ** 304502210090538DC25A2F8FA9AB251C43D74351C524906188DEA497315A7EF9FA4D56D5210220001C61BA47FBA968F975A0A2381B2B9C1128C1B0C622AA8AA263CB08BA1F788B
**1.57报错签名: ** 3045022100B9BEA487F8E7D53950F197129CB3F2071E6F72B397891D28956B1FBA249A09EB0220208CFF213212A6D311CB4E8D122BE020E924CBC1E033B14C4BDFA12EE2BFD8D9
附解释测试代码
//签名值十六进制
String signvalue = "3045022100B9BEA487F8E7D53950F197129CB3F2071E6F72B397891D28956B1FBA249A09EB0220208CFF213212A6D311CB4E8D122BE020E924CBC1E033B14C4BDFA12EE2BFD8D9";
//签名值转二进制
Byte[] signValueData = Hex.decode(signvalue );
//读取LEN1
int len1 = (int) signValueData [1];
//读取LEN2
int len2 = signValueData [3];
//计算LEN3
int len3 = len1 - len2 - 4;
//计算R值长度&R值PADDING长度
int padRLen = len2 - 32;
int rLen = 32;
if(len2 < 32){
rLen = len2;
padRLen = 0;
}
//读取R值
byte[] rByte = readField(signValueData , 4 + padRLen, rLen);
if(len3>32){
len3 = 32;
}
//读取S值
byte[] sByte = readField(signValueData , signValueData .length - len3, len3);
byte[] rsByte = mergeByte(rByte, sByte);
return rsByte;
- 相关方法代码
/**
* 合并字节数组
*
* @param bt1 字节数组1
* @param bt2 字节数组2
* @return
* @throws Exception
*/
public static byte[] mergeByte(byte[] bt1, byte[] bt2) {
byte[] dest = new byte[bt1.length + bt2.length];
System.arraycopy(bt1, 0, dest, 0, bt1.length);
int offset = bt1.length;
System.arraycopy(bt2, 0, dest, offset, bt2.length);
return dest;
}
/**
* 读取数据域
*
* @param data 数据
* @param fieldIndex 数据域起始索引
* @param fieldLen 数据域数据长度
* @return
*/
public static byte[] readField(byte[] data, int fieldIndex, int fieldLen) {
byte[] filedData = new byte[fieldLen];
System.arraycopy(data, fieldIndex, filedData, 0, fieldLen);
return filedData;
}
/**
* ASN1格式签名值转RS格式签名值
* @param asn1Base64Signature
* @return
*/
public static byte[] toRSSignature(String asn1Base64Signature) {
byte[] signatureByte = Base64.getDecoder().decode(asn1Base64Signature);
return toRSSignature(signatureByte);
}
/**
* ASN1格式签名值转RS格式签名值
* @param asn1Signature
* @return
*/
public static byte[] toRSSignature(byte[] asn1Signature) {
int len1 = (int) asn1Signature[1];
int len2 = asn1Signature[3];
int len3 = len1 - len2 - 4;
int padRLen = len2 - 32;
int rLen = 32;
if(len2 < 32){
rLen = len2;
padRLen = 0;
}
byte[] rByte = EMUtil.readField(asn1Signature, 4 + padRLen, rLen);
if(len3>32){
len3 = 32;
}
byte[] sByte = EMUtil.readField(asn1Signature, asn1Signature.length - len3, len3);
byte[] rsByte = EMUtil.mergeByte(rByte, sByte);
return rsByte;
}
解密问题场景:
和验签类似,此问题是由于在读取解析网联格式ASN.1(C1C3C2)的密文值转换传递给加密机(加密机密文值格式:C1C3C2 )解密的过程中导致的一个报错。
问题场景参数:
网联密文值格式: ASN.1(C1C3C2)
加密机密文值格式:C1C3C2
转换解析过程: org.bouncycastle.asn1.ASN1Sequence.getInstance(网联密文值)
BC包版本:1.57
异常信息:java.lang.IllegalArgumentException: failed to construct sequence from byte[]: corrupted stream detected
异常情况: 随机出现
问题原因
网联格式ASN.1(C1C3C2)的密文值转换传递给加密机(加密机密文值格式:C1C3C2 )解密的过程报错,推断BC包1.57版本在解释网联加密数据值的ASN1数据格式有问题
解决方案:
根据网联ASN.1密文值格式来解析读取C1C3C2 格式的密文值
-
加密数据的ASN.1 数据结构如下:
CipherData ::= SEQUENCE {
xcoordinate INTEGER,
ycoordinate INTEGER,
hash OCTET STRING,
cigherText OCTET STRING
} -
C1C3C2格式对应:
c1 = xcoordinate + ycoordinate
c3 = hash;
c2 = cigherText; -
格式解析如下:
30 + totalLen + xTag + xlen + x + yTag + ylen +y + c3Tag + c3len + c3 + c2Tag + c2len + c2; -
示例数据:
307c022100e739650179d8dea27e17c126c927a4c9934090b8921599bbe6463aff546ed7b5022021de9b9b3ca414c3e4d947cfb0df1a146caa427ee41cd687c4eed23097b9ea4e04200eb2f3e3f230cd0e5dd6d109d24c0913b76cb4e2a48e554a0018bd0b69b8e21d04130b5f55e5b98ee53bc43cdb55b730114cf75fd6
附网联国密ASN.1加密数据转C1C3C2格式密文测试代码
- 直接上代码
/**
* 加密数据的ASN.1 数据结构如下:
* CipherData ::= SEQUENCE {
* xcoordinate INTEGER,
* ycoordinate INTEGER,
* hash OCTET STRING,
* cigherText OCTET STRING
* }
*
* C1C3C2格式对应:
* c1 = xcoordinate + ycoordinate
* c3 = hash;
* c2 = cigherText;
*
* 格式解析如下:
* 30 + totalLen + xTag + xlen + x + yTag + ylen +y + c3Tag + c3len + c3 + c2Tag + c2len + c2;
* @paramasn1Data
* @throwsIOException
*/
publicstaticbyte[] asn1CipherDataToc1c3c2(byte[] asn1Data) throwsIOException {
inttotalLen = (int) asn1Data[1];
intxlen = (int) asn1Data[3];
intylen = (int) asn1Data[5 + xlen];
intc3len = (int) asn1Data[7 + xlen + ylen];
intc2len = totalLen - xlen - ylen - c3len - 8;
System.out.println("totalLen>>" + totalLen);
System.out.println("xlen>>" + xlen);
System.out.println("ylen>>" + ylen);
System.out.println("c3len>>" + c3len);
System.out.println("c2len>>" + c2len);
byte[] xByte = EMUtil.readField(asn1Data, 4, xlen);
byte[] yByte = EMUtil.readField(asn1Data, 6 + xlen, ylen);
xByte = fixToCurveLengthBytes(xByte);
yByte = fixToCurveLengthBytes(yByte);
byte[] c3Byte = EMUtil.readField(asn1Data, 8 + xlen + ylen, c3len);
byte[] c2Byte = EMUtil.readField(asn1Data, asn1Data.length - c2len, c2len);
byte[] c1c3c2Byte = mergeC1c3c2Byte(xByte, yByte, c3Byte, c2Byte);
returnc1c3c2Byte;
}
/**
* 过滤大数补位,转为Curve长度字节
* @paramsrc
* @return
*/
privatestaticbyte[] fixToCurveLengthBytes(byte[] src) {
if (src.length == CURVE_LEN) {
returnsrc;
}
byte[] result = newbyte[CURVE_LEN];
if (src.length > CURVE_LEN) {
System.arraycopy(src, src.length - result.length, result, 0, result.length);
} else {
System.arraycopy(src, 0, result, result.length - src.length, src.length);
}
returnresult;
}
/**
* 合并c1c3c2数据
* @paramxByte
* @paramyByte
* @paramc3
* @paramc2
* @return
*/
privatestaticbyte[] mergeC1c3c2Byte(byte[] xByte, byte[] yByte, byte[] c3, byte[] c2) {
byte[] c1c3c2 = newbyte[xByte.length + yByte.length + c3.length + c2.length];
System.arraycopy(xByte, 0, c1c3c2, 0, xByte.length);
intoffset = xByte.length;
System.arraycopy(yByte, 0, c1c3c2, offset, yByte.length);
offset += yByte.length;
System.arraycopy(c3, 0, c1c3c2, offset, c3.length);
offset += c3.length;
System.arraycopy(c2, 0, c1c3c2, offset, c2.length);
returnc1c3c2;
}