什么是 TLV
TLV(Tag-Length-Value) 是一种灵活的数据编码格式,广泛应用于智能卡、金融支付、网络协议等领域。它通过”标签-长度-值”三元组的方式组织数据,使得数据结构具有良好的可扩展性和自描述性。
基本结构
1 2 3 4 5
| +--------+--------+------------------+ | Tag | Length | Value | +--------+--------+------------------+ | 标签 | 长度 | 数据 | +--------+--------+------------------+
|
- Tag(标签):标识数据的类型或用途,通常为 1-2 个字节
- Length(长度):表示 Value 部分的字节数,可变长度
- Value(值):实际的数据内容,长度由 Length 字段指定
TLV 的应用场景
1. 银行卡 IC 卡数据
1 2 3 4 5
| 示例数据:57 09 4333353138303631329000 解析结果: Tag: 57 (卡号) Length: 09 (9字节) Value: 4333353138303631329000 (实际卡号数据)
|
2. EMV 支付终端
3. 网络协议
4. 物联网设备
TLV 数据格式详解
Tag(标签)字段
单字节 Tag
1 2 3 4 5 6 7 8 9 10
| 二进制结构: ┌─────┬─────┬────────┐ │ b8 │ b7 │ b6-b1 │ ├─────┼─────┼────────┤ │类别 │构造 │标签号 │ └─────┴─────┴────────┘
b8-b7: 00=Universal, 01=Application, 10=Context, 11=Private b6: 0=基本型(Primitive), 1=构造型(Constructed) b5-b1: 标签号(1-30)
|
示例:
1 2 3 4
| 0x57 = 01010111 ├─ 01: Application类 ├─ 0: 基本型 └─ 10111: 标签号23(卡号)
|
多字节 Tag
当标签号大于 30 时,使用多字节表示:
1 2 3 4 5 6
| 第一字节:b5-b1全为1(0x1F) 后续字节:最高位为1表示继续,为0表示结束
示例:0x9F61 0x9F = 10011111 (第一字节,b5-b1=11111) 0x61 = 01100001 (第二字节,最高位=0,结束)
|
Length(长度)字段
短格式(0-127 字节)
1 2
| 0x00 - 0x7F: 直接表示长度 示例:0x09 = 9字节
|
长格式(128 字节以上)
1 2 3 4 5 6 7 8 9
| 第一字节:0x81-0x84 0x81: 后续1个字节表示长度 0x82: 后续2个字节表示长度 0x83: 后续3个字节表示长度 0x84: 后续4个字节表示长度
示例:0x82 01 00 0x82: 使用2字节表示长度 0x01 0x00: 长度为256字节
|
Value(值)字段
基本型(Primitive)
Value 包含实际的数据内容:
1 2
| Tag: 57 (卡号) Value: 直接存储卡号数据
|
构造型(Constructed)
Value 包含嵌套的 TLV 结构:
1 2 3 4 5
| Tag: 70 (数据模板) Value: 包含多个子TLV结构 └─ TLV1: 57 09 43... (卡号) └─ TLV2: 5F20 0C ... (持卡人姓名) └─ TLV3: 9F61 12 ... (身份证号)
|
常见编码方式
1. 十六进制(HEX)
1 2
| 原始数据:57 09 4333353138303631329000 用途:调试、日志记录
|
2. BCD 编码(Binary Coded Decimal)
1 2 3
| 数值:6225123456789012 BCD: 62 25 12 34 56 78 90 12 用途:卡号、金额
|
3. ASCII 编码
1 2 3
| 文本:"VISA" ASCII: 56 49 53 41 用途:简单字符串
|
4. UTF-8/GBK 编码
1 2 3 4
| 中文:"张三" GBK: D5 C5 C8 FD UTF-8: E5 BC A0 E4 B8 89 用途:持卡人姓名等
|
🎨 银行卡常用 Tag
卡片信息
| Tag |
名称 |
编码方式 |
示例 |
| 57 |
卡号(Track 2) |
BCD |
62251234567890129000 |
| 5A |
应用主账号(PAN) |
BCD |
6225123456789012 |
| 5F20 |
持卡人姓名 |
GBK/UTF-8 |
张三 |
| 5F24 |
应用失效日期 |
BCD |
251231 (2025 年 12 月 31 日) |
交易相关
| Tag |
名称 |
编码方式 |
说明 |
| 9F02 |
授权金额 |
BCD |
000000010000 (100.00 元) |
| 9F03 |
其他金额 |
BCD |
000000000000 |
| 9F1A |
终端国家代码 |
ASCII |
CN (中国) |
| 9F33 |
终端性能 |
HEX |
E0F8C8 |
持卡人信息
| Tag |
名称 |
编码方式 |
说明 |
| 9F61 |
持卡人证件号码 |
ASCII/BCD |
身份证号 |
| 9F62 |
持卡人证件类型 |
HEX |
00=身份证 |
解析流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 原始数据:57 09 4333353138303631329000
第1步:读取Tag ├─ 读取:0x57 ├─ 判断:单字节Tag(b5-b1 ≠ 11111) ├─ 类型:基本型(b6=0) └─ 结果:Tag = "57"
第2步:读取Length ├─ 读取:0x09 ├─ 判断:短格式(< 0x80) └─ 结果:Length = 9字节
第3步:读取Value ├─ 读取:43 33 35 31 38 30 36 31 32 ├─ 后续:90 00 └─ 结果:Value = 9字节数据
第4步:解析Value ├─ Tag=57 → 卡号 ├─ 编码:BCD ├─ 处理:去除D后的数据 └─ 结果:433351380631329
|
TLV 的优势
1. 灵活性
1 2 3
| 可以任意添加新的Tag,无需修改已有结构 原有数据:57 09 ... (卡号) 添加数据:57 09 ... + 5F20 0C ... (姓名)
|
2. 可扩展性
1 2 3 4
| 支持嵌套结构,可以表示复杂的数据关系 70 1A (数据模板) 57 09 ... (卡号) 5F20 0C ... (姓名)
|
3. 自描述性
1 2 3 4
| 通过Tag就能知道数据的含义,不需要额外的文档 57 = 卡号 5F20 = 持卡人姓名 9F79 = 卡片余额
|
4. 向后兼容
1
| 旧系统可以忽略不认识的Tag,继续处理已知的数据
|
解析注意事项
1. Tag 字节数判断
1 2 3 4 5
| if ((firstByte & 0x1F) == 0x1F) { }
|
2. Length 格式判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| if (length <= 0x7F) { } else if (length == 0x80) { } else { int lenOfLen = length & 0x7F; }
|
3. 构造型递归解析
1 2 3 4 5 6 7 8
| bool isConstructed = (firstByte & 0x20) != 0;
if (isConstructed) { ParseTlvRecursive(value); }
|
4. 编码方式选择
1 2 3 4 5 6 7 8
| switch (tag) { case "57": case "5F20": case "4F": case "9F61": }
|
实际应用示例
银行卡读卡数据
1 2 3 4 5 6 7 8 9 10 11
| 原始数据: 70 29 57 09 62 25 12 34 56 78 90 12 90 00 5F20 06 D5 C5 C8 FD 9F79 06 00 00 00 01 00 00
解析结果: 数据模板 (70) ├─ 卡号 (57): 6225123456789012 ├─ 持卡人姓名 (5F20): 张三 └─ 卡片余额 (9F79): 100.00元
|
支付交易数据
1 2 3 4 5 6
| 原始数据包含: - 交易金额 - 交易类型 - 终端信息 - 卡片信息 - 认证数据
|
总结
TLV 格式的核心优势:
- 结构清晰 - Tag-Length-Value 三部分职责明确
- 易于扩展 - 新增字段不影响现有结构
- 自我描述 - 通过 Tag 即可知道数据含义
- 嵌套支持 - 支持复杂的层次化数据结构
在下一篇文章中,我们将展示如何用 C#实现完整的 TLV 解析器,支持递归解析、多种编码方式和银行卡数据处理。
理解 TLV 格式是处理智能卡、金融支付等领域数据的基础。掌握其原理后,你就能轻松应对各种 IC 卡数据解析需求!