前言
隔离上篇文章IC卡(智能卡)APDU通讯总结太久了,这次整理一下TLV数据解析的教程,供大家参考。有时候发送指令读取到IC卡数据,直接转 ASCII码就可以拿到自己想要的数据,和业务交互。但是银行卡读取到的报文数据了,直接转是行不通的。本文重点是数据解析,不是讲解与卡怎么发送指令通讯,发送什么指令(这种都是大同小异,遵循中国金融集成电路(IC)卡规范)。
TLV
PBOC(The People’s Bank of China 中国人民银行)的银行IC卡大部分数据都是 TLV(tag-length-value) 格式的(或者叫IC卡55域数据)。 TLV(BER(Basic Encoding Rules)是 ASN.1 中最早定义的编码规则,BER 传输语法的格式一直是 TLV 三元组) 是 tag, length 和 value 的缩写,tag是这个数据元的标示,length是这个数据元值的部分的长度,value则是该数据元的值。
Tag(标签)
注:字节排序方向为从左往右数,第一个字节即为最左边的字节。bit排序规则同理。
可以用下面一张图表示:
tag在pboc中最多占两个字节,第一个字节的编码规则 。
b7 和 b6 两位标识 tag 所属类别 。00表示TLV描述的是基本数据类型(Primitive Frame, int,string,long…),01表示用户自定义类型(Private Frame,常用于描述协议中的消息
b5 决定当前的 TLV 数据是一个单一的数据(Primitive Data 编码)和复合结构的数据(Constructed Data编码) 。 复合的 TLV 是指 value 域里也包含一个或多个 TLV, 类似嵌套的编码格式 。
b4~b0 如果全为 1 ,则说明这个 tag下面还有一个子字节,占两个字节,否则tag占一个字节 。如果tag占用两个字节,第二个字节的编码格式, b7决定tag是否还有后绪的字节存在,bit7为1时存在后续字节,为0时不存在后续字节。bit6~bit0:Tag正文
举例说明:
- 9F33(9F:1001 1111):为一个占用两个字节的tag标签。
- 95(1001 0101):为一个占用一个字节的tag标签。
Length(长度)
length占1~3个字节长度,描述Value部分所占字节的个数,编码格式分两类:定长方式(DefiniteForm)和不定长方式(IndefiniteForm),其中定长方式又包括短形式与长形式。
定长方式
短形式: 字节第7位为0(最左边字节的最左bit位(即bit7为0)),表示Length使用1个字节即可满足Value类型长度的描述,它的后续7个bit位(即bit6~bit0)表示Value取值长度的范围在0~127之间的,把后续7bit转成十进制值即可表示Value的字节长度。
举例说明:
03(0000 0011):表示Value占用三个字节,所以,若Value的长度在1~127字节之间,那么该L字段本身仅占一个字节
长形式: 字节第7位为1(最左边字节的最左bit位(即bit7为1)),表示Length使用多个字节描述Value类型长度,Value类型的长度大于127时,bit6~bit0用来描述Length值占用的字节数,把bit6~bit0转成十进制值即可表示Value的字节长度。
举例说明:
81FF(1000 0001 1111 1111):1000 0001表示Value为长形式,占用一个字节,其Value的字节数为1111 1111即255个字节
所以,若Value的长度在128~255字节之间,那么该L字段本身**仅占两个字节 **
不定长方式
Length所在八位组固定编码为0x80,但在Value编码结束后以两个0x00结尾。这种方式使得可以在编码没有完全结束的情况下,可以先发送部分数据给对方。
举例说明:
1000 000 … 00:1000 000表示Value长度,value最后的两位是00
Value(数值)
由一个或多个值组成 ,值可以是一个原始数据类型(Primitive Data),也可以是一个TLV结构(Constructed Data)
单一结构(原始数据类型):T L V
复合结构(复合TLV结构):T L V(嵌套一个以上TLV)
示例解析说明
上面解释了TLV的组成原理,下面举例进行分析,最后附上解析代码。上面说了TLV数据格式有可能TLV嵌套TLV,编码解析使用递归解析。
下面是一个完整的TLV数据格式(复合结构,单一结构比较简单,不做举例说明),APDU正常通讯成功后,取返回数据Data和SW1 SW2,不记得APDU通讯数据格式请看IC卡(智能卡)APDU通讯总结
704D5A0A6221871000001018326F8E0C000000000000000002031F009F0D05D86004A8009F0E0500109800009F0F05D86804F8005F24032608315F280201569F0702FF005F25031608239F080200309000
9000: 正常 成功执行
704D5A0A6221871000001018326F8E0C000000000000000002031F009F0D05D86004A8009F0E0500109800009F0F05D86804F8005F24032608315F280201569F0702FF005F25031608239F08020030:要解析的数据
为方便观察,整理成表格,请点击查看,完整解析如下图:
代码实现TLV解析
基于上面TLV组成原理编码实现如下:
核心解码类MyTlvDecode
:
public class MyTlvDecode {/*** 递归解析TLV格式数据** @param tlvHexStr* @param tlvs*/
public void decodeTLV(String tlvHexStr, List<TLV> tlvs) {if (tlvHexStr == null || tlvHexStr.length() == 0) {throw new NullPointerException("tlvHexStr 为null.");}if (tlvHexStr.length() % 2 != 0) {throw new IllegalArgumentException("tlvHexStr 参数非法.");}byte[] bytes = ByteUtils.hexStringToByteArr(tlvHexStr);decodeTLV(bytes, bytes.length, tlvs);
}/*** 递归解析TLV格式数据** @param tlvBytes* @param len* @param tlvs*/
public void decodeTLV(byte[] tlvBytes, int len, List<TLV> tlvs) {if (tlvBytes == null || tlvBytes.length == 0) {throw new NullPointerException("tlvBytes 为null.");}System.out.println(ByteUtils.byteArrToHexString(tlvBytes));byte[] tag;int vLength;int lLength;boolean complexTag = false;//bit与运算同为1才为1,0x20=00100000, b5=1是判断单一结构还是复合结构if ((tlvBytes[0] & 0x20) != 0x20) { // 单一结构if ((tlvBytes[0] & 0x1f) != 0x1f) { // tag占用一个字节,否则占用两个字节(b0-b5都是1)vLength = tlvBytes[1] == 0x81 ? tlvBytes[2] : tlvBytes[1];lLength = 2;//不定长方式if (vLength > 0x80) {lLength = 3;//定长方式}tag = new byte[1];} else {// tag为两个字节vLength = tlvBytes[2] == 0x81 ? tlvBytes[3] : tlvBytes[2];lLength = 3;if (vLength > 0x80) {lLength = 4;}tag = new byte[2];}if (vLength < 0) {throw new RuntimeException("TLV解码异常, vLength:" + vLength);}} else { // 复合结构complexTag = true;if ((tlvBytes[0] & 0x1f) != 0x1f) { // tag为一个字节vLength = tlvBytes[1] == 0x81 ? tlvBytes[2] : tlvBytes[1];lLength = 2;if (vLength > 0x80) {lLength = 3;}tag = new byte[1];} else {// tag为两个字节vLength = tlvBytes[2] == 0x81 ? tlvBytes[3] : tlvBytes[2];lLength = 3;if (vLength > 0x80) {lLength = 4;}tag = new byte[2];}if (vLength < 0) {throw new RuntimeException("TLV解码异常,tag:" + tag + ",vLength:" + vLength);}}//分别解析出T、L、VSystem.arraycopy(tlvBytes, 0, tag, 0, tag.length);byte[] value = new byte[vLength];System.arraycopy(tlvBytes, lLength, value, 0, value.length);String tagStr = ByteUtils.byteArrToHexString(tag);String tagValue = ByteUtils.byteArrToHexString(value);int tagLength = value.length;tlvs.add(new TLV(tagStr, tagLength, tagValue));if (complexTag){decodeTLV(value, tagLength, tlvs);}if (len > vLength + lLength) {byte[] nextTlv = new byte[len - (vLength + lLength)];System.arraycopy(tlvBytes, vLength + lLength, nextTlv, 0, nextTlv.length);decodeTLV(nextTlv, nextTlv.length, tlvs);}
}/*** @param args 测试程序*/public static void main(String[] args) {MyTlvDecode t = new MyTlvDecode();List<String> tlvArr = new ArrayList<>();tlvArr.add("704D5A0A6221871000001018326F8E0C000000000000000002031F009F0D05D86004A8009F0E0500109800009F0F05D86804F8005F24032608315F280201569F0702FF005F25031608239F080200309000");for (int i = 0; i < tlvArr.size(); i++) {List<TLV> tlvs = new LinkedList<>();long start = System.currentTimeMillis();byte[] bytes = ByteUtils.hexStringToByteArr(tlvArr.get(i));t.decodeTLV(bytes, bytes.length, tlvs);// t.decodeTLV(tlvStr, tlvs);long end = System.currentTimeMillis();System.out.println("解析耗时:" + (end - start) + "ms");for (TLV tlv : tlvs) {System.out.println("tag:" + tlv.tag + ",length:" + tlv.length + ",value:" + tlv.value);}System.out.println("\n");}}}
实体TLV
:
public class TLV {/** 标签 */public String tag;/** 长度 */public int length;/** 值 */public String value;public TLV(){}public TLV(String tag, int length, String value) {this.length = length;this.tag = tag;this.value = value;}//...}
运行结果如下:
解析耗时:1ms
tag:70,length:77,value:5A0A6221871000001018326F8E0C000000000000000002031F009F0D05D86004A8009F0E0500109800009F0F05D86804F8005F24032608315F280201569F0702FF005F25031608239F08020030
tag:5A,length:10,value:6221871000001018326F
tag:8E,length:12,value:000000000000000002031F00
tag:9F0D,length:5,value:D86004A800
tag:9F0E,length:5,value:0010980000
tag:9F0F,length:5,value:D86804F800
tag:5F24,length:3,value:260831
tag:5F28,length:2,value:0156
tag:9F07,length:2,value:FF00
tag:5F25,length:3,value:160823
tag:9F08,length:2,value:0030
然后对比文档就知道上面tag的含义了,上图表格中已经标标出
小结
本文主要介绍TLV的组成原理和解析方法,解析不保证100%通用,但适合大部分业务需求。TLV数据解析这种不难,多读几遍和实践几次,就可以熟能生巧。