[TOC]
术语表
术语 | 英文 | 解释 |
---|---|---|
EMV | Europay, MasterCard and Visa | |
ISD | Issuer Secure Domains | 发卡方安全域 |
SD | Secure Domains | 安全域 |
DM SD | ||
DES | Data Encryption Standard | 数据加密标准 |
DEK | Data Encryption Key | 数据加密密钥 |
TDES | Triple DES | 3DES 算法 |
MAC | Message Authentication Code | 消息认证码 |
C-MAC | Command Message Authentication Code | |
KMC DES | Master Key for Personalization Session Keys | |
ICV | Initial Chaining Vecto | 初始向量 |
CM | CardManger | |
SCP | Secure Channel Protocol | 安全通道协议 |
AID | Application Identifier | 应用标识符 |
APDU | Application Protocol Data Unit | 应用协议数据单元 |
CLA | Class byte of the command message | 命令消息中的类字节 |
INS | Instruction byte of the command message | |
P1 | Reference control parameter 1 | 引用控制参数1 |
P2 | Reference control parameter 2 | 引用控制参数2 |
Lc | Exact length of data in a case 3 or case 4 command | |
Le | Maximum length of data expected in response to a case 2 or case 4 command | |
SW | Status Word | 状态字 |
SW1 | Status Word One | |
SW2 | Status Word Two | |
TLV | Tag Length Value | |
OPEN | GlobalPlatform envisequenguochengceronment | |
ENC | Encryption | 加密 |
打开安全通道
打开安全通道主要包括三步
- 选中 ISD
- INITIALIZE UPDATE 完成对卡的认证
- EXTERNAL AUTHENTICATION 完成卡内 对卡外实体的认证
Select ISD
不同规范的卡ISD 不同, 我们使用的是GP211 ISD的AID 是a000000151000000 Select类型的APDU的数据段中需要指定ISD AID,这里有一个小技巧 如果不知道ISD AID情况可以发送APDU指令中不带数据段,就会默认选中发卡发安全域.
INITIALIZE UPDATE
在安全通道的显式发起期间, INITIALIZE UPDATE 命令用于在卡和主机之间传送卡和会话数据。这个命令开始一个安全通道会话的发起。
在当前安全通道的任何时候,都可以将 INITIALIZE UPDATE 命令发送给卡以发起一个新的安全通道会话。
发送APDU消息格式
INITIALZE UPDATE 编码表
编码 | 值 | 解释 |
---|---|---|
CLA | 0x80 | |
INS | 0x50 | INITIALIZE UPDATE |
P1 | xx | |
P2 | 0x00 | |
Lc | 0x08 | host challenge 长度 |
Data | xxxxx | host challenge |
Le | 0x00 | 返回长度任意或者说是最大255 |
这里主要要介绍 host challenge 这是一个8 bytes的 随机数,在每次会话中是唯一, 卡收到 host challenge 后用 静态秘钥 和自己的 card challenge sequence counter 加密生成 card cryptogram (卡的鉴别码) 然后返回给卡外实体(28 byte ).
相应消息
响应消息中包含下列数据的串联(没有分隔符):
名字 | 长度 |
---|---|
秘钥推导数据 | 10 bytes |
秘钥信息 | 2 bytes |
sequence counter | 2 bytes |
card challenge | 6 bytes |
card cryptogram | 8 bytes |
- 密钥派生数据一般由后端系统用来派生卡静态密钥.(这个暂时还没用到)
- 秘钥信息里面包含了秘钥的版本号和scpVersion 标识符.
- sequence counter 是卡内一个递增的计数器,他用于创建会话秘钥.
- card challenge 是卡内部生成的随机数.
- 卡认证码 这个用于卡外实体认证卡的合法性.
首先介绍下sequence counter
安全域将为每一个安全通道基本密钥或共享相同密钥版本号的(一组)安
全通道密钥管理一个序列计数器。
当且仅当安全通道(隐式或显式发起)的第一个 C-MAC 被验证为有效时,序列计数器才加 1。
它在命令具体处理前增加(即校验 C-MAC 之后,执行具体命令之前)。在安全通道密钥
的创建或更新时,序列计数器重置为 0。当安全通道密钥集包含多于一个密钥时,密钥集中任意一
个密钥创建或更新,序列计算器重置为 0。
注意:如果 C-MAC 不是安全通道中第一个 C-MAC,或者当一个安全通道会话已经开始并且
C-MAC 无效,序列计数器不会更新。从这个方面来说,序列计数器保持了到目前为止有效的安全通
道会话的个数,经历过的相应的安全消息密钥集。当达到最大值时,序列计数器将不会重置为 0。
不要求卡支持超过 32767 的计数器值。
再介绍下card cryptomgram, host cryptomgram , 生成规则. 这两个鉴别码完成了双向认证. 由于sequence
counter 是由安全域维护的一个计数序列, 且在每次会话中唯一,所以可以用这两bytes 为基础,然后填充协议要求的数据,然后推导出本地会话过程中的过程秘钥信息, 推导依赖与 静态秘钥,协议填充数据 , 加密算法
- 首先卡外实体和卡有相同的对称秘钥信息(S-MAC , S-ENC , DNC)
- 协议填充数据
- 生成安全通道 C-MAC 会话密钥:需要用到以下数据,安全通道基本密钥或者 MAC 密钥(S-MAC),一个值为‘0101’的会话密钥派生数据. | 0101 | sequence counter | 00(12 bytes) |
- 生成安全通道 R-MAC 会话密钥:需要用到以下数据,安全通道基本密钥或者 MAC 密钥(S-MAC),一个值为‘0102’的会话密钥派生数据 . | 0102 | sequence counter | 00(12 bytes) |
- 生成安全通道加密会话密钥:需要用到以下数据,安全通道基本密钥或者加密密钥(S-ENC),一个值为‘0182’的会话密钥派生数据. | 0182 | sequence counter | 00(12 bytes) |
- 生成安全通道数据加密会话密钥:需要用到以下数据,安全通道基本密钥或者数据加密密钥(DEK),一个值为‘0181’的会话密钥派生数据 . | 0181 | sequence counter | 00(12 bytes) |
加密算法 推导过程秘钥 Session-ENC Session-MAC Session-DEK, 这里用的是3DES算法 初始ICV 为8 bytes 0x00数据, 模式是CBC模式,由于这里加密的数据是8的倍数 所以指定nopadding. 加密秘钥为相应的静态秘钥( 由于3DES 算法的加密秘钥是24 bytes 而相应的静态秘钥为16 bytes 这里做法是把 24 bytes 最后8 bytes 用静态秘钥的前8 bytes 填充),具体的实现细节如下.
1234567891011121314151617KeySet sessionKeys = new KeySet();try {byte[] derivationData = new byte[16];derivationData[2] = seq1;derivationData[3] = seq2;byte[] constantMAC = new byte[] {(byte) 0x01, (byte) 0x01};System.arraycopy(constantMAC, 0, derivationData, 0, 2);ICipher cipher = ICipher.Factory.getImplementation(ICipher.DESEDE_CBC_NOPADDING, GPUtil.getKey(staticKeys.keys[1], 24), new byte[8]);// Session-MACsessionKeys.keys[1] = cipher.encrypt(derivationData);
推导过程秘钥的示例图如下:
- card cryptomgram 生成校验, 需要用到卡外产生host challenge, 卡返回的sequence counter , card challenge和规范要求的填充数据, 这里用的的是mac_3des 算法(组合算法),用到的加密秘钥是 Session-ENC 具体实现看算法具体的注释.
card cryptomgram 生成校验过程(可以推算卡内也是这样算出来的, 只有具有相同的静态秘钥,按照相应的协议,才能生成通样的鉴别数据)
mac_3des 算法
这里初始向量cv 8 bytes 全 0x00 数据, key 是过程秘钥然后按照之上的规则扩展24 bytes , 加密数据是|host challenge(8 bytes)| + | sequenguochengce counter (2 bytes)|+| card challenge (6 bytes)| +|8000000000000000( 8 bytes)|
- host cryptogram 生成用来认证卡外实体的合法性, 具体细节基本上和 card cryptogram 生成类似, 只不过加密数据有所变化,这里只是颠倒了随机数 | sequence counter (2 bytes)|+| card challenge (6 bytes)|+|host challenge(8 bytes) | +|8000000000000000( 8 bytes)|123456789101112try {bo.reset();// sequence counter + card challengebo.write(result, 12, 8);// host challengebo.write(rand);} catch (IOException ioe) {}// 加密数据是| sequence counter (2 bytes)|+| card challenge (6 bytes)|+|host challenge(8 bytes) | +|8000000000000000( 8 bytes)|byte[] authData = GPUtil.mac_3des(sessionKeys.keys[0], GPUtil.pad80(bo.toByteArray()), new byte[8]);
接下来就进入了卡认证卡外实体的过程.
EXTERNAL AUTHENTICATE
本认证过程主要对指令做了C-MAC 签名 保证数据的不被篡改, 这里涉及加密算法的 和 ICV 以及明文组装.这个 C-MAC 是卡外实体对要发送到卡内的 APDU 指令(包括 APDU 指令的头,以及命令消息中的数据段,但不包括 Le)进行 MAC 运算后生成的。
ICV 用于链接 APDU 命令以保证这些命令顺序的完整性;。对于第一个成功执行 C-MAC 校验之后的命令来说,它们在进行 C-MAC 运算时所采用的 ICV 是前一个命令的 C-MAC 值.
为了反映在命令消息中存在 C-MAC 值,APDU 命令的头应该按照以下方式进行修改:
- 在命令消息的长度字段(Lc)中应该加 8 以表明命令消息字段中存在 C-MAC
- 命令类型(Cla)应该被修改以表明该 APDU 命令中包含了安全消息。这是通过设置命令类型字节的第 3 位来完成的。对于本规范中定义个所有 APDU 命令来说,包含后安全消息的 APDU 命令的命令类型都应该是‘0x84’。C-MAC 的生成和校验不影响命令类型中包含的逻辑通道的相关信息:在生成和校验 C-MAC 时,这里一直假定逻辑通道号为 0。C-MAC 被附加在 APDU 命令消息的最末端,这个 APDU 命令消息不包含填充数据,但是包含修改后的 APDU 命令头。
- 在未修改过的 APDU 命令上进行 C-MAC 生成:在执行 C-MAC 计算之前,应该先对 APDU命令进行填充,在 C-MAC 生成之后修改 APDU 命令头。
- 在修改后的 APDU 命令上进行 C-MAC 生成:在执行 C-MAC 运算之前,先进行 APDU命令的填充和 APDU 命令头的修改。
卡里为了校验卡外实体发来C-MAC,必须使用和卡外实体相同的数据填充机制,使用相同的ICV,使用相同的C-MAC会话密钥来对这个C-MAC进行校验。这个被成功校验的C-MAC值应该被保留,以作为下一个命令C-MAC校验时的ICV。无论这个APDU命令是否成功完成,卡内从来都不应该丢掉前一个命令中成功校验的C-MAC值
数据组装
装载安装Applet
装载Applet 涉及的APDU 指令比较多,而且需要很多装载参数, 用到INSTLL( for load or install ) LOAD, 装载的前提是先打开安全通道,也是之上的过程,之后要获取Applet相关信息,这个信心可以从Apllet的cap文件获取,然后就是分段上传数据到卡,最后更改cap文件带的Applet的状态为selectable, 所以这里一切前提就是先是从cap 文件里面获取相关信息.
流程如下:
cap 分析
cap 文件其实是一个jar包,可以这么理解相当与java的jar包,里面还包括部分标记信息,这个信息有的是用来调试,,有的是为了作为安装使用,这里只是简单分析cap格式和获取想关信息,后期有所需求在继续深入分析.
JAVA 智能的可执行文件(CAP 文件)是编译多个应用程序(Applet)的生成结果,包含了一个包中定义的所有类和接口,与包之间是一一对应的关系。实际发卡操作时,首先需要将该可执行文件下载至卡片中,并安装需要的应用实例;用户使用该安装的应用实例执行操作功能。
CAP文件包含12个组件:
Component Type | Value |
---|---|
COMPONENT_Header | 1 |
COMPONENT_Directory | 2 |
COMPONENT_Applet | 3 |
COMPONENT_Import | 4 |
COMPONENT_ConstantPool | 5 |
COMPONENT_Class | 6 |
COMPONENT_Method | 7 |
COMPONENT_StaticField | 8 |
COMPONENT_ReferenceLocation | 9 |
COMPONENT_Export | 10 |
COMPONENT_Descriptor | 11 |
COMPONENT_Debug | 12 |
一个完整的CAP文件,除Applet、Export 和Debug组件是可选外,其他均为必选。每个组件封装成一个CAP包,包含在Jar包中。最后在卡上只保留了5个组件:COMPONET_Method,COMPONET_Class,COMPONET_ConstantPool,COMPONET_StaticField和 COMPONET_Export。其余的组件只是安装时提取有用信息而不在卡中保存。
12个组件中,类class组件保存本应用声明的所有类和接口的信息; 方法method组件保存本应用声明的所有方法和接口,method中利用2字节索引index引用类、方法和域;常数池constant pool组件保存method组件引用的所有类、方法和域信息,分为类、实例域、虚方法、父方法、静态域和静态方法6类,每组信息为4个字节;相关地址reference location组件保存method组件中索引的偏移。
对于JavaCard而言,应用程序的下载过程是即CAP文件写入到EEPROM的过程,即是对CAP文件的下载过程。在CAP文件的下载过程中,需要将一部分组件进行解析,同时对reference location中指定的位置进行链接,能够链接到method组件中的一个索引号,并根据索引号查找constant pool中保存的、与该索引号对应的类、方法或域在 EEPROM中的实际地址,调用实际地址中存储的数据。也就是说,方法的调用其实是需要两个步骤来实现的:
- 根据reference location中指定的位置进行链接,获取method组件中的索引号;
根据索引号查找constant pool中保存的、与该索引号对应的类、方法或域在EEPROM中的实际地址,调用实际地址中存储的数据。
注:多字节数据总是按照大端(big-endian)顺序存放。
INSTALL[for load] 所需要的数据信息
下表描述了 INSTALL[for load]命令的数据字段:
装载文件数据块 Hash 和装载 Token 对于委托管理是必须的。如果装载文件包含一个或多个 DAP
块,那么装载文件数据块 Hash 是强制的。在其它情况下,装载文件数据块 Hash 是可选的并且是可
以被卡验证的。
装载文件 AID 和装载参数将与装载文件数据块(如果有的话)中包含的信息是一致的。
我们可以cap中获取 装载文件 AID。可以从cap里面的header.cap 文件里面分析获取, 其实这里还需要计算cap文件 里面相关文件的hash,其实是计算cap里面一组文件拼接后的HASH
Header.cap 格式
Header组件中包含了该CAP文件的基本信息,其中包括最重要的文件版本信息和包AID值。版本信息用以判断JAVA卡是否支持对该文件的解析。AID是CAP文件的唯一识别,单张JAVA智能卡上不支持装载相同AID的CAP文件。
|
|
其中CAP File Package Flags:
Flags | value |
---|---|
ACC_INT | 0x01 |
ACC_EXPORTACC_EXPORT | 0x02 |
ACC_EXPORT | 0x04 |
|
|
以 HelloWorld.cap 文件中的Header.cap中的信息为例:
0×01 | 0×00 0×13 | 0xDE 0xCA 0xFF 0xED | 0×01 | 0×02 | 0×04 |0×00 0×01 | 0×09 | 0xA0 0×00 0×00 0×00 0×62 0×03 0×01 0x0C 0×01
- tag: 0×01 COMPONENT_Header
- size: 0×13 = 19 除去tag和size后的大小
- magic: 0xDECAFFED 用以标识Java卡CAP文件格式,若一个文件不是以0xDECAFFED开头则肯定不是JavaCard CAP文件,因为它不符合规范
- minor_version: 0×01 次版本号
- major_version: 0×02 主版本号, 该CAP文件格式的版本号是02.01;如果CAP文件的版本号超出了Java卡虚拟机所能够处理的有效范围,Java卡虚拟机将不会处理该CAP文件
- flag: 0×04 包中没有用到int类型、CAP文件中没有Export组件、CAP文件中有Applet组件。
- package_info:
- minor_version: 0×00
- major_version: 0×01
- AID_length: 0×09
- AID: 0xA0 0×00 0×00 0×00 0×62 0×03 0×01 0x0C 0×01
获取包AID java 代码实现
Applet.cap 分析
Applet组件用以记录CAP文件中Applet的基本信息。CAP文件中的Applet可以有多个,且相互独立。 这里主要为了获取应用的AID,为INSTALL [ for make selectable ] 做准备,更改应用状态.
|
|
以 HelloWorld.cap 中的 Applet.cap为例:
0×03 | 0×00 0x0E | 0×01 | 0x0A 0xA0 0×00 0×00 0×00 0×62 0×03 0×01 0x0C 0×01 0×01 0×00 0×14
- tag: 03 COMPONENT_Applet
- size: 0x0E = 14
- count: 01 包中有一个applet
- applets[]:
- AID_length: 0x0A = 10
- AID[]: 0xA0 0×00 0×00 0×00 0×62 0×03 0×01 0x0C 0×01 0×01 该applet的AID
- install_method_offset: 0×14 即当前Applet中的install()在Method组件info[]中的偏移。其中,Install()方法是一个静态方法,用以创建当前Applet的类实例,赋予应用软件生命周期开始执行。
|
|
拼INSTALL [for load ]指令
install for load 发起装载请求 上面基本介绍过APDU 带的数据段,这里只用确定P1 P2 就可以拼接APDU 发起load 请求.
P1 = 0x02
P2 = 0x00
相关过程代码
|
|
一系列的LOAD APDU 指令
为装载一个装载文件的 LOAD 命令的数据字段中传输的装载文件的结构
多个 LOAD 命令可以用来传输一个装载文件到卡中。这个装载文件被分成多个便于传输的小块。
每一个 LOAD 命令将从 ̳00 开始进行编号。LOAD 命令的编号是连续的,每次增加 1。装载文件的最后一块时将通知卡。
当接收到装载文件的最后一块后,卡将执行先于 LOAD 命令的对装载文件必要的内部处理和在INSTALL[for load]命令中标识的任何附加的处理。
LOAD APDU
代码 | 值 | 含义 |
---|---|---|
CLA | 0x80或 ̳0x84 | |
INS | ̳0xE8 | LOAD |
P1 | xx | 引用控制参数 P1 |
P2 | xx | 块号 |
Lc | xx | 数据字段的长度 |
Data | xxxxx | 装载数据(和 MAC 如果存在) |
Le | 0x00 | qi |
引用控制参数P1
下表描述了引用控制参数 P1 的编码,该参数用于指示命令消息中包含的块是否是最后一块。
load 过程实现
|
|
其中涉及到如何把cap文件里面的数据组装和分组,这个到看实现 这里就先不写了
拼INSTALL [for install] APDU指令
下表描述了 INSTALL[for install]命令的数据字段
装载文件数据块 Hash 和装载 Token 对于委托管理是必须的。如果装载文件包含一个或多个 DAP
块,那么装载文件数据块 Hash 是强制的。在其它情况下,装载文件数据块 Hash 是可选的并且是可
以被卡验证的。
装载文件 AID 和装载参数将与装载文件数据块(如果有的话)中包含的信息是一致的。
相关java 代码