IC卡(银行卡)APDU数据格式TLV解析

前言

隔离上篇文章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排序规则同理。

可以用下面一张图表示:

xBAnAO.png

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:要解析的数据

为方便观察,整理成表格,请点击查看,完整解析如下图:

xBlq9x.png

代码实现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、V
    System.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数据解析这种不难,多读几遍和实践几次,就可以熟能生巧。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值