几乎所有的需要在卡片和终端之间传送的数据都是TLV格式的.
TLV是tag, length和value的缩写.一个基本的数据元就包括上面三个域. Tag唯一标识该数据元, length是value域的长度. Value就是数据本身了. 举个例子, 下面是一个tlv格式的AID(应用标识符)字节串”9F0607A0000000031010”, 其中9F06是tag, 07是长度, A0000000031010就是AID本身的值了.
对于程序编写人员来说,如果有类似上面这样的一串TLV编码的字节串从卡片传过来, 怎么样从中提取我们想要的数据. 这就牵扯出TLV解码的问题了
TLV一种可变格式,TLV的意思就是:Type类型, Lenght长度,Value值;
Type和Length的长度固定,一般那是2、4个字节;
Value的长度有Length指定;
解析方法:
1.读取type 转换为ntohl、ntohs转换为主机字节序得到类型;指针偏移+2或4
2.读取lenght,转换为ntohl、ntohs转换为主机字节序得到长度;指针偏移+2或4
3.根据得到的长度读取value,指针偏移+Length;
。。。。
继续处理后面的tlv;
TLV编码就是指先对Tag编码,再对Length编码,最后对Value编码。BER编码的长度确定的编码方式就是这样的。
BER编码有两种方式:
一种是长度确定的编码方式。这由3部分组成Identifier octets、Length octets和Contents octets(可以和TLV对应)。另一种是长度不确定的编码方式。这由4部分组成Identifier octets、Length octets、Contents octets、End-of-contents octets。其中Length octets为0x80,End-of-contents octets为0x00 00。每种类型都能够编码成长度确定的编码方式,但是有的类型不能够编码成长度不确定的编码方式。DER编码只能使用长度确定的编码方式。
Identifier octets由3部分组成Class、P/C和Tag number。Identifier octets的第一个字节的高2位为Class,接下来一位为P/C,其他位表示Tag number。Class有4中类型Universal(00)、Application(01)、Context-specific(10)和Private(11)。P/C位如果为1则表示是Constructed的,为0表示是Primitive。如果0<=Tag number<=30,则整个Identifieroctets只有一个字节,否则第一个字节的后5位前为1,接下来找第一个最高位为0的字节,该字节就是Identifier octets的最后一个字节。从第二个字节到最后一个字节去掉最高位的值拼起来就是Tagnumber的值。
长度确定的编码方式的Length octets有两种方法编码长度,一种是只用一个字节表示长度,其最高位为0,后7位表示长度值,显然这样只能表示0-127。另一种是第一个字节的最高位为1,其他位表示后面还有多少个字节属于Length octets。后面的那些字节组成的就是长度值。长度值表示的是Contents octets所占的字节数。DER要求如果长度为0-127则要使用第一种方式,如果大于127则使用后一种方式。
其中BER-TLV编码是ISO定义一种规范,然后到了PBOC/EMV里被简化了, 哪里被简化了呢?举一个例子, tag域在ISO里可以有多个字节, 而PBOC/EMV里规定只用前两个字节. 我下面要讲的TLV解码就是基于PBOC/EMV的简化版本.
首先看一下tag域是怎样编码的. Tag域占最多占两个字节. 编码规则如下面两幅图一下这两幅图. 第一个图是第一个字节的编码规则. b8和b7两位标识tag所属类别. 这个可以暂时不用理. b6决定当前的TLV数据是一个单一的数据和复合结构的数据. 复合的TLV是指value域里也包含一个或多个TLV, 类似嵌套的编码格式. b5~b1如果全为1,则说明这个tag下面还有一个子字节.占两个字节, 否则tag占一个字节.
第二幅图是说明如果tag占用两个字节, 第二个字节的编码格式. B8决定tag是否还有后绪的字节存在,因为前面说过,PBOC/EMV里的tag最多占两个字节,所以该位保持为0.
清楚了上面tag编码格式,可很容易写出tag域解码的代码了. 假设,终端接收到一人字节串,这个字节串保存在tlvData的字节数组里, 伪代码如下:
if ( (tlvData[i]&0x20) != 0x20)//单一结构
{
if ((tlvData[i]&0x1f) == 0x1f)//tag两字节
{
tagIndex++;
//解析length域
//解析value域
}
else//tag单字节
{
//解析length域
//解析value域
}
}
else//复合结构
{
//复合结构可以考虑用递归的方法来实现.
}
Length域的编码比较简单,最多有四个字节, 如果第一个字节的最高位b8为0, b7~b1的值就是value域的长度. 如果b8为1, b7~b1的值指示了下面有几个子字节. 下面子字节的值就是value域的长度.