金融系统中PBOC/EMV的TLV的算法实现(含C++/C#)

TLV即Tag-Length-Value,常在IC卡与POS终端设备中通过这样的一个应用通信协议进行数据交换。在金融系统以及认证中,PBOC以及 EMV的认证规范文档上面也有对TLV做了一些说明,由于认证规范都是英文文档,所以有些人可能不易于理解。首先我先介绍下什么是TLV,TLV的用途是 什么,以及如何实现它的打包解包算法。

金融系统中的TLV是BER-TLV编码的一个特例编码规范,而BER-TLV是ISO定义中的规范。在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度

其实,在BER编码的方式有两种情况,一种是确定长度的方式,一种是不确定长度的方式,而金融TLV选择了确定长度的方式,这样在设备之间的数据传输量上就可以减少。

Tag域说明:

先来看下TLV的Tag是如何编码的,先看下图:

这张图说明了Tag域第一个字节的编码格式。其中b8-b1代表1个字节中的8个位。

其中b8,b7代表数据的类别。根据2个位的组合,有四种类别:通用类别,应用类别,上下文语境类别,专用类别。这个主要用于在于终端设备交互的时候,确定数据处理的类型。

b6代表的是数据元结构,也就是说它是属于简单数据元结构,还是属于结构(复合)数据元结构。当b6为1的时候,就需要后续的字节进行扩展。也就是说复合的TLV中,value域里也包含一个或多个TLV,这个稍后接着介绍。

当b5-b1代表串行号,当5个位都为1时,需要将tag域扩展到下一个字节中,也就是Tag占2个字节;而当5个位都不全为1时,该Tag域就只占1个字节。

现在,看下b5-b1:11111的情况:

从图中我们看到BER-TLV编码中,当b8为1时,Tag还需要有后续字节,直到b8为0为止。从EMV文档中的说明,Tag最多只占用2个字节,所以这样就相对比较简单一些了。当b8为0时,该Tag域结束,总共就占用2个字节。

Length域说明:

在文档中没有图片叙述,我自绘一个:

当b8为0时,该字节的b7-b1作为value域的长度;当b8为1时,b7-b1作为后续字节的长度,也就是说,例如有这样一个 值:10000011,代表后续还有3个字节作为value域的长度(本字节不算,本字节变为作为一个Length的索引)。3个字节代表value的长 度,意味着什么呢,意味着内容的长度当需要很大的时候,字节的位数就会跟着越高,3个字节就代表最大可以有256*256*256的长度。

Value域说明:

也是分成两种情况考虑,就是前面说到的Tag分成两个数据元结构,一种是简单数据元结构,一种是复合数据元架构:

先来看看简单数据元结构:

Tag就是Tag,没有子标签Tag,基本结构就是T-L-V。

再看下符合数据元结构:

后面的Value说明:Primitive or constructed BER-TLV data object number,包含一个简单数据元结构或者也可以是一个符合数据元结构。这样可以看出,算法中必须要实现Tag的嵌套功能,递归算法不可少。

算法实现:

根据以上的说明现在来实现它的打包解包的算法(打包的目的是将一个从终端上发的请求数据——字节数组,构造成一系列的TLV结构实体;解包的目的刚好相反,就是将TLV结构实体解析成字节数组,然后通过IC卡发送到终端上)。

首先定义一个TLV结构实体:

1

2

3

4

5

6

7

8

9

// TLV结构体

struct TLVEntity {

    unsigned char* Tag;         //标记

    unsigned char* Length;      //数据长度

    unsigned char* Value;       //数据

    unsigned int TagSize;       //标记占用字节数

    unsigned int LengthSize;    //数据长度占用字节数

    TLVEntity* Sub_TLVEntity;   //子嵌套TLV实体

};

其 中TagSize代表Tag字段的字节长度,LengthSize代表Length的字节长度,这里的Length记住要使用char*,由于前面说 过,Length可能包含多个字节,通过多个字节确定Value域的长度,Sub_TLVEntity作为子嵌套的TLV结构体。

定义一个TLVPackage的打包类:

TLVPackage.h:

1

2

3

4

5

6

7

8

9

10

11

// TLV打包类

class TLVPackage

{

public:

    TLVPackage();

    virtual ~TLVPackage();

    //构造TLV实体

    static void Construct(unsigned char* buffer, unsigned int bufferLength, TLVEntity* tlvEntity, unsigned int& entityLength, unsigned int status=0);

    //解析TLV字节数组

    static void Parse(TLVEntity* tlvEntity, unsigned int entityLength, unsigned char* buffer, unsigned int& bufferLength);

};

具体方法实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

// 构造TLV

void TLVPackage:: Construct(

    unsigned char* buffer,

    unsigned int bufferLength,

    TLVEntity* tlvEntity,

    unsigned int& entityLength,

    unsigned int status

    )

{

    int currentTLVIndex = 0;

    int currentIndex = 0;

    int currentStatus = 'T'//状态字符

    unsigned long valueSize = 0;

    while(currentIndex < bufferLength)

    {

        switch(currentStatus)

        {

        case 'T':

            valueSize = 0;

            //判断是否单一结构

            if((status == 1 && buffer[currentIndex] & 0x20) != 0x20)

            {

                tlvEntity[currentTLVIndex].Sub_TLVEntity = NULL; //单一结构时将子Tag置空

                //判断是否多字节Tag

                if((buffer[currentIndex] & 0x1f) == 0x1f)

                {

                    int endTagIndex = currentIndex;

                    while((buffer[++endTagIndex] & 0x80) == 0x80); //判断第二个字节的最高位是否为1

                    int tagSize = endTagIndex - currentIndex + 1; //计算Tag包含多少字节

                    tlvEntity[currentTLVIndex].Tag = new unsigned char[tagSize];

                    memcpy(tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, tagSize);

                    tlvEntity[currentTLVIndex].Tag[tagSize] = 0;

                    tlvEntity[currentTLVIndex].TagSize = tagSize;

                    currentIndex += tagSize;

                }

                else

                {

                    tlvEntity[currentTLVIndex].Tag = new unsigned char[1];

                    memcpy(tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, 1);

                    tlvEntity[currentTLVIndex].Tag[1] = 0;

                    tlvEntity[currentTLVIndex].TagSize = 1;

                    currentIndex += 1;

                }

            }

            else

            {

                //判断是否多字节Tag

                if((buffer[currentIndex] & 0x1f) == 0x1f)

                {

                    int endTagIndex = currentIndex;

                    while((buffer[++endTagIndex] & 0x80) == 0x80); //判断第二个字节的最高位是否为1

                    int tagSize = endTagIndex - currentIndex + 1; //计算Tag包含多少字节

                    tlvEntity[currentTLVIndex].Tag = new unsigned char[tagSize];

                    memcpy(tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, tagSize);

                    tlvEntity[currentTLVIndex].Tag[tagSize] = 0;

                    tlvEntity[currentTLVIndex].TagSize = tagSize;

                    currentIndex += tagSize;

                }

                else

                {

                    tlvEntity[currentTLVIndex].Tag = new unsigned char[1];

                    memcpy(tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, 1);

                    tlvEntity[currentTLVIndex].Tag[1] = 0;

                    tlvEntity[currentTLVIndex].TagSize = 1;

                    currentIndex += 1;             

                }

                //分析SubTag

                int subLength = 0; 

                

                unsigned char* temp;

                if((buffer[currentIndex] & 0x80) == 0x80)

                {

                    for (int index = 0; index < 2; index++)

                    {

                        subLength += buffer[currentIndex + 1 + index] << (index * 8); //计算Length域的长度

                    }

                    temp = new unsigned char[subLength];

                    memcpy(temp, buffer + currentIndex + 3, subLength);

                }

                else

                {

                    subLength = buffer[currentIndex];

                    temp = new unsigned char[subLength];

                    memcpy(temp, buffer + currentIndex + 1, subLength);

                }

                temp[subLength] = 0;

                

                //memcpy(temp, buffer + currentIndex + 1, subLength);

                unsigned int oLength;

                tlvEntity[currentTLVIndex].Sub_TLVEntity = new TLVEntity[1];

                Construct(temp, subLength, tlvEntity[currentTLVIndex].Sub_TLVEntity, oLength);

            }

            currentStatus = 'L';

            break;

        case 'L':      

            //判断长度字节的最高位是否为1,如果为1,则该字节为长度扩展字节,由下一个字节开始决定长度

            if((buffer[currentIndex] & 0x80) != 0x80)

            {

                tlvEntity[currentTLVIndex].Length = new unsigned char[1];

                memcpy(tlvEntity[currentTLVIndex].Length, buffer + currentIndex, 1);

                tlvEntity[currentTLVIndex].Length[1] = 0;

                tlvEntity[currentTLVIndex].LengthSize = 1;

                valueSize = tlvEntity[currentTLVIndex].Length[0];

                currentIndex += 1;

            }

            else

            {

                //为1的情况

                unsigned int lengthSize = buffer[currentIndex] & 0x7f;

                

                currentIndex += 1; //从下一个字节开始算Length域

                for (int index = 0; index < lengthSize; index++)

                {

                    valueSize += buffer[currentIndex + index] << (index * 8); //计算Length域的长度

                }

                tlvEntity[currentTLVIndex].Length = new unsigned char[lengthSize];

                memcpy(tlvEntity[currentTLVIndex].Length, buffer + currentIndex, lengthSize);

                tlvEntity[currentTLVIndex].Length[lengthSize] = 0;

                tlvEntity[currentTLVIndex].LengthSize = lengthSize;

                currentIndex += lengthSize;

            }

            currentStatus = 'V';

            break;

        case 'V':

            tlvEntity[currentTLVIndex].Value = new unsigned char[valueSize];

            memcpy(tlvEntity[currentTLVIndex].Value, buffer + currentIndex, valueSize);

            tlvEntity[currentTLVIndex].Value[valueSize] = 0;

            currentIndex += valueSize;

            

            //进入下一个TLV构造循环

            currentTLVIndex += 1;

            currentStatus = 'T';

            break;

        default:

            return;

        }

    }

    entityLength = currentTLVIndex;

}

// 解析TLV

void TLVPackage::Parse(

    TLVEntity* tlvEntity,

    unsigned int entityLength,

    unsigned char* buffer,

    unsigned int& bufferLength

    )

{

    int currentIndex = 0;

    int currentTLVIndex = 0;

    unsigned long valueSize = 0;

    while(currentTLVIndex < entityLength)

    {

        valueSize = 0;

        TLVEntity entity = tlvEntity[currentTLVIndex];

        

        memcpy(buffer + currentIndex, entity.Tag, entity.TagSize);  //解析Tag

        currentIndex += entity.TagSize;

        for (int index = 0; index < entity.LengthSize; index++)

        {

            valueSize += entity.Length[index] << (index * 8); //计算Length域的长度

        }

        if(valueSize > 127)

        {

            buffer[currentIndex] = 0x80 | entity.LengthSize;

            currentIndex += 1;

        }

        

        memcpy(buffer + currentIndex, entity.Length, entity.LengthSize);    //解析Length

        currentIndex += entity.LengthSize;

        //判断是否包含子嵌套TLV

        if(entity.Sub_TLVEntity == NULL)

        {

            memcpy(buffer + currentIndex, entity.Value, valueSize); //解析Value

            currentIndex += valueSize;

        }

        else

        {

            unsigned int oLength;

            Parse(entity.Sub_TLVEntity, 1, buffer + currentIndex, oLength); //解析子嵌套TLV

            currentIndex += oLength;

        }

        currentTLVIndex++;

    }

    buffer[currentIndex] = 0;

    bufferLength = currentIndex;

}

然后写测试程序:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

// 上发测试数据

unsigned char requestBuf[] = {

        0x9F, 0x1C, 0x12, 0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32, 0x30, 0x34, 0x30, 0x34,

        0x32, 0x37, 0x31, 0x38, 0x9F, 0x62, 0x01, 0x01, 0x57, 0x12, 0x62, 0x22, 0x89, 0x00, 0x00, 0x02, 0x91,

        0x01, 0xD0, 0x90, 0x32, 0x01, 0x02, 0x47, 0x17, 0x13, 0x00, 0x0F, 0x5F, 0x20, 0x0A, 0x48, 0x55, 0x47,

        0x55, 0x4F, 0x20, 0x4D, 0x49, 0x4E, 0x47, 0x9F, 0x1F, 0x3C, 0x25, 0x39, 0x39, 0x36, 0x32, 0x32, 0x32,

        0x38, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x39, 0x31, 0x30, 0x31, 0x5E, 0x47, 0x55, 0x4F, 0x20,

        0x4D, 0x49, 0x4E, 0x47, 0x2F, 0x48, 0x55, 0x5E, 0x30, 0x39, 0x30, 0x33, 0x32, 0x30, 0x31, 0x30, 0x32,

        0x34, 0x37, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x38, 0x39, 0x30,

        0x30, 0x3F

};

    TLVEntity tlvEntity[TLV_MAX_LENGTH];

    unsigned int tlv_count;

    //构造TLV

    TLVPackage::Construct(requestBuf, sizeof(requestBuf), tlvEntity, tlv_count);

    unsigned char parseBuf[1024];

    unsigned int buf_count;

    //解析TLV

    TLVPackage::Parse(tlvEntity, tlv_count, parseBuf, buf_count);

    if(strncmp((char*)parseBuf, (char*)requestBuf, sizeof(requestBuf)) == 0)

    {

        AfxMessageBox("TRUE");

    }

    else

    {

        AfxMessageBox("FALSE");

    }

最后测试结果中,可以得到最后将弹出“TRUE”的对话框。证明构造TLV得到的TLVEntity,再对这个实体进行解析,可以的到解析后的字节数组,最后通过strncmp的方法比较判断,是否原始字节数组和解析后的字节数组是否一致。

另外,为了方便C#程序员的使用,我对该C++程序重新改写了一下,得出的结果也是一样的。

TLVEntity.cs

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

/// <summary>

    /// TLV实体

    /// </summary>

    public class TLVEntity

    {

        /// <summary>

        /// 标记

        /// </summary>

        public byte[] Tag { getset; }

        /// <summary>

        /// 数据长度

        /// </summary>

        public byte[] Length { getset; }

        /// <summary>

        /// 数据

        /// </summary>

        public byte[] Value { getset; }

        /// <summary>

        /// 标记占用字节数

        /// </summary>

        public int TagSize { getset; }

        /// <summary>

        /// 数据长度占用字节数

        /// </summary>

        public int LengthSize { getset; }

        /// <summary>

        /// 子嵌套TLV实体

        /// </summary>

        public TLVEntity Sub_TLVEntity { getset; }

    }

以及TLVPackage.cs

接着,写测试程序:

运行结果:

https://www.cnblogs.com/zeng-/p/4754900.html

总结

TLV在数据通信方面,其实运用得很广泛,在应用层数据通信中,如HTTP协议,HTML,XML语言本身定义了一些标签 (Body,Head,Script)对数据串行化,接收方再根据标签解析原始数据,通过浏览器展现出来。因此本质上也是属于TLV协议的设计模式。甚至 在传输层的TCP,UDP协议,你完全可以通过TLV实现自定义的应用协议。

金融系统中PBOC/EMV的TLV的算法实现(含C++/C#)相关推荐

  1. 融系统中PBOC/EMV的TLV的算法实现(含C++/C#)

    TLV即Tag-Length-Value,常在IC卡与POS终端设备中通过这样的一个应用通信协议进行数据交换.在金融系统以及认证中,PBOC以及EMV的认证规范文档上面也有对TLV做了一些说明,由于认 ...

  2. C#:实现PBOC/EMV的TLV的算法(附完整源码)

    C#:实现PBOC/EMV的TLV的算法 public class TLVEntity{/// <summary>/// 标记/// </summary>public byte ...

  3. 金融系统中正确的金额计算及存储方式

    转载自 金融系统中正确的金额计算及存储方式 经典的精度丢失问题 Java中的类型float.double用来做计算会有精度丢失问题,下面来看下面的示例. public static void main ...

  4. 金融系统中加密机的简介

    金融系统中加密机的简介 加密机是一台大小和台式PC机箱差不多大小的一台设备,价格较贵,约6-8万/台,在银行.银联.第三方支付等金融机构广泛使用,主要用来加解密银行卡密码,计算交易MAC,保证交易中敏 ...

  5. c语言拓扑多边形自动生成,GIS系统中多边形矢量数据自动拓扑的算法

    (1)建立GIS多边形拓扑关系的概念 顺时针方向构多边形 所谓顺时针方向构多边形是指多边形在链的右侧(图a),多边形在闭合曲线内.逆时针方向构多边形是指多边形在链的左侧(图b),多边形在闭合曲线外. ...

  6. ROS系统中实现点云聚类(realsense数据源)

    本文主要介绍ROS系统中如何订阅并解码realsense点云数据,并对点云进行稀疏.去噪.聚类. 环境配置见<ROS系统中从零开始部署YoloV4目标检测算法(3种方式)> 需要安装的第三 ...

  7. php执行md5sum,Linux_详解Linux系统中md5sum命令的用法,MD5算法常常被用来验证网络文 - phpStudy...

    详解Linux系统中md5sum命令的用法 MD5算法常常被用来验证网络文件传输的完整性,防止文件被人篡改.MD5全称是报文摘要算法(Message-Digest Algorithm 5),此算法对任 ...

  8. 《房债》书中的精髓:现代金融系统让全世界的人同处一艘经济大船上,一些人搞坏了船,所有的人都跟着遭殃。

    <房债>书中的精髓:现代金融系统让全世界的人同处一艘经济大船上,一些人搞坏了船,所有的人都跟着遭殃. 开始于2007年的次贷危机,之所以造成非常严重的后果,最可能的原因是大部分华尔街的人, ...

  9. PBOC/EMV 中SDA和DDA简介

    PBOC/EMV里有两个非常重要的概念: SDA(staticdataauthentication)和DDA(dynamicdataauthentication),分别叫做静态数据认证和动态数据认证. ...

最新文章

  1. PyTorch框架:(5)使用PyTorch框架构建卷积神经网络
  2. 耐能团队论文登上《自然·电子学》:集成忆阻器与CMOS以实现更好的AI
  3. 使用python编写单元测试
  4. 002_Maven命令
  5. Lync-技巧-2.启用-用户-启用-语音-设置-线路URI
  6. 时隔多日,旧域名重用,niceyoo博客上线
  7. 阻止跳转的四种方式,你知道吗?
  8. spring boot: 一般注入说明(五) @Component, application event事件为Bean与Bean之间通信提供了支持...
  9. log4j.properties配置文件
  10. html简单实现下拉菜单
  11. APP原生开发与APP混合开发的区别
  12. 出国旅行 Android软件,出国旅行必备实用app,你拥有几款
  13. 代码打累了看看短腿基!
  14. java正则匹配ip_正则表达式匹配ip地址
  15. cada0图纸框_CAD的图框应该怎么画-百度经验
  16. 无敌python爬虫教程学习笔记(一)
  17. 在uni-app的textarea中输入纯数字或者英文不换行的问题
  18. 从开发者到讲师的心路历程
  19. windows找不到文件gpedit.msc怎么办?
  20. 爬虫—微博博主动态及相册的请求构造规律

热门文章

  1. PHP安装包下载地址
  2. 大数据之Flume:Flume概述
  3. synonymous oracle,oracle dblink
  4. nodejs的package.json配置参数
  5. 51NOD - 1305 Pairwise Sum and Divide(思维)
  6. 私有云的部署(详细)
  7. 如何使用glove,fasttext等词库进行word embedding?(原理篇)
  8. C++之父B.Stroustrup言论
  9. 软件生成问候图片_图片生成器软件-图片生成器下载 v1.0免费版--pc6下载站
  10. BDT和XO的应用心得