手动与交通联合卡交互,并实现读取元数据/余额

手动与交通联合卡交互,并实现读取元数据/余额

0x01 前言

农历春节将近,假期快要尾声。而我,却有点倒霉。众所周知,本人以前对普通的Mifare Classic卡有点研究,但是像交联卡这样的cpu卡还是没有研究过的。所以昨天我给自己找了一点小活,就是研究研究手上的交通联合卡,看看能不能手动和它交互,顺带学习一下cpu卡的交互原理。

鸣谢项目:https://github.com/SocialSisterYi/T-Union_Master

0x02 一点前置知识

交通联合卡的交互标准在交通部 JT/T 978 规范中描述,而这个规范也遵守银行卡普遍采用的EMV标准。

APDU格式

作为CPU卡,它底层使用ISO14443-4和读卡器交互,在更高层级上使用APDU格式来传输命令和数据。APDU格式结构:

向卡片发送命令:CLA INS P1 P2 Lc Data Le,除了Data之外都是一字节。CLA为指令类别,INS为指令码,P1 P2为指令的参数,Lc是Data的长度,如果Data为空,则Lc和Data均省略,Le是期望获得的响应最大长度,通常为0,指返回所有可用数据。

卡片的响应: Data SW1 SW2,Data是返回的响应数据,为不定长数组(可为空),最后两字节SW1和SW2构成状态字,比如SW1==0x90&&SW2==0x00代表成功。

TLV格式

在卡片的响应APDU Data是使用TLV格式封装的。它的格式就如其名一样Tag(标签)-Length(长度)-Value(数据)。Tag可以是一字节也可以是多个字节。Tag的首字节的b8b7两位指定了tag的类型,此处不详细展开,b6代表这个块的value嵌套有多个子tlv。b5-b1全为1代表Tag有第二个字节,第二个字节开始的b8为1同时b7-b1大于0代表还有下一个字节,以此类推,实际情况上基本是全都是1字节2字节,没见过三字节及以上的。Length很好理解,一个字节代表Value的长度。Value就是真实的数据。

比如6f 49 84 0e … 6f就是tag,49就是value的长度,0e开始的0x49个字节则是value。

有个在线工具可以解析TLV结构:https://emvlab.org/tlvutils

0x03 交互流程

相关流程图在 JT/T 978.3—2023+城市公共交通IC卡技术规范 第3部分:读写终端 的12页。

和卡片交互主要是使用SELECT命令,其在 JT/T 978.2—2023城市公共交通IC卡技术规范 第2部分 卡片 的附录C中规定。

采用手机上的NFC Tools PRO里的其他->先进的NFC命令(应该是高级的NFC命令吧),IOClass选IsoDep。

读取Meta(卡号、签发日期等)

SELECT命令(APDU格式): 00 a4 04 00 (文件名长度) (文件名) 00,响应根据选中的文件而有所不同,具体参阅JT/T 978.2 附录C。

  1. 选中PPSE,它会返回这张卡支持的应用列表(电子现金、电子钱包等等),因为这两个应用规范里都写了所以我们可以省略掉这一步。

    • 发送: 00:A4:04:00:0E:32:50:41:59:2E:53:59:53:2E:44:44:46:30:31:00 (SELECT “2PAY.SYS.DDF01”)

    • 接收:6f49840e325041592e5359532e4444463031a537bf0c3461194f08a000000632010106500a4d4f545f545f4341534887010161174f08a00000063201010550084d4f545f545f45508701029000

    把收到的Data部分(除去尾部的90 00,代表成功)按照TLV格式解码,可以看到它支持两个应用,一个是MOT_T_CASH(电子现金),一个是MOT_T_EP(电子钱包)。我们关注的是第二个MOT_T_EP,它包含了这张卡的元数据,比如发卡机构、卡号、签发日期、到期日期等等。它的AppId是A000000632010105。

  2. 选中电子钱包应用,获取该卡的meta。

    • 发送:00:A4:04:00:08:4D:4F:54:5F:54:5F:45:50:00 (SELECT 0xA000000632010105)

    • 接收:6f318408a000000632010105a5259f0801029f0c1e01011000ffffffff020103105170010170xxxxxx201904152040123100009000

    将收到的Data解析,可以获知应用版本号是02,还有一个Tag是9f0c的就是我们关心的元数据。

    01011000FFFFFFFF020103105170010170xxxxxx20190415204012310000

    偏移长度(字节)说明例子
    0x01卡类型0x01 普通卡
    0x22发卡地0x1000 北京
    0xa10卡号,bcd编码0x03105170010170xxxxxx
    0x144签发日期,四字节YYMD bcd编码0x20190415
    0x184到期日期,同上0x20401231

读取余额

获取余额可以独立于前面两步,它有专门的一条命令(GET BALANCE,APDU格式):80 5c 00 02 04

这条命令也有一些变形,可以查询已经透支金额之类,详见JT/T 978.2 C3.11 查询余额命令。

  • 发送:80 5c 00 02 04

  • 接收:000011f89000

Data是一个大端序uint32的数,单位是分,比如这个就是4600(0x000011f8)分钱也就是46块钱。

还可以通过其他命令来读这张卡的支付历史,从哪站到哪站等等,感兴趣可以去规范文件里读。

0x04 结语

最近有点倒霉闹心,遂学新技术一点,随便水博客一篇,好歹有点收获。。。祝自己新年快乐


手动与交通联合卡交互,并实现读取元数据/余额
https://www.hakurei.org.cn/2026/02/14/tunion-card-manual/
作者
zjkimin
发布于
2026年2月14日
更新于
2026年2月14日
许可协议