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

1,082 阅读8分钟

前言

隔离上篇文章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数据解析这种不难,多读几遍和实践几次,就可以熟能生巧。