cpu卡发卡,消费,圈存

601 阅读3分钟

cpu卡的发卡,消费,圈存过程

cpu卡的cos系统

目前了解的cpu厂商有山东华翼,上海复旦微电子;各个厂商又会有多个不同的芯片型号 比如华翼微 HYm4616A3,上海复旦FM1280;典型的应用有公交卡、消费、银行卡、社保卡、健康卡、门禁、身 份识别等应用

Psam卡

psam卡作为密钥的载体,会存储密钥。可以直接写入或者从加密机中获取。 在进行消费的时候psam会进行验证密钥的正确性

cpu卡发卡

  1. 创建主控目录FID为3F00(默认) 80E03F001638FFFFCA009572FF315041592E5359532E4444463031

  2. 创建主目录密钥文件 (文件id)(data长度)(3F采用加密+mac写入) (文件空间0050)
    80E0 0000 07 FF 0050 FFCFFF00

  3. 追加主控密钥(密钥类型为39) 80D4 01 00 15 39 0000FF33+16字节密钥 (01追加密钥或者密钥类型为更改密钥) (00为密钥索引)

  4. 追加应用维护密钥(密钥类型为36) 80D40100153600000033+16字节密钥

  5. 创建3F01目录 80E03F0111381000CF009573FFA00000000386980701

  6. 创建钱包目录下的密钥文件 80E0000007FF02000000FF00

  7. 追加如下密钥类型的密钥文件

1658136179890.jpg

8.创建15文件(基本信息文件)

80E0001507 A8 001E0000FF FD A8以MAC的方式进行更新 FD使用线路保护02的密钥索引来计算MAC(FD的计算根据下图把二进制转成16进制)

1658136537548.jpg 9. 更新15文件

`04D6950022000000010000000001010001${asn}20220606204206060000`; asn为8字节长度用户卡号
15文件格式如下图

1658385288817.jpg

至此发卡完成。

消费

  1. 寻卡获取到卡片的UID,最后一个字节为20的代表卡类型为CPU卡
  2. 复位操作
  3. 选择互联互通应用即发卡时创建钱包目录A00000000386980701 返回结果包含发卡时写入的数据。根据发卡方代码来进行分割
val basicInfoIndex = walletResult.indexOf("XXXX")
val basicInfo = walletResult.substring(basicInfoIndex + 6)
cityCode = basicInfo.substring(4, 8)
//卡号
asn = basicInfo.substring(24, 40)
  1. 如果是使用住建部规则,则需要先进行住建部认证操作
终端向卡片发送80CA000009 获取认证码
终端向PSAM卡发送80CA000009+认证码
结果返回9000代码表示认证成功
  1. 进行消费初始化 805001020B 01 + 00000001 + 6字节psam卡编号 从结果中获取电子钱包交易序号atc和伪随机数
//电子钱包交易序号
val atc = initConsumerResp.substring(8, 12)
//伪随机数
val rnd = initConsumerResp.substring(22, 30)

1658386118806.jpg

  1. 终端向PSAM发送指令计算mac
"8070000024$rnd$atc${amountHex}06${datetimeStr}0100$asn${PSamCardInit.cityCode}FF0000000000".pSamOperating()
  1. 终端向CPU卡发送消费交易指令
val consumeResp ="805401000F$terminalCounter$datetimeStr${mac1}08".cPUOperating()
val mac2 = consumeResp.substring(8, 16)

8.终端向PSAM卡发送MAC2认证指令

"8072000004$mac2".pSamOperating()
如果返回结果为9000 代表消费成功

至此消费流程全部结束

圈存交易

  1. 进行消费前三步操作
  2. 验证个人pin 取决于发卡时是否写入了个人pin,如果没写可以省略验证
  3. 进行圈存
val loadInitRst =805000020B01$amountHex${terminalId}10".cPUOperating()

//旧余额
val oldBalance = loadInitRst.substring(0, 8)
//交易序号
val trn = loadInitRst.substring(8, 12)
val rnd = loadInitRst.substring(16, 24)
val mac1 = loadInitRst.substring(24, 32)
//生成过程密钥 4随机数 + 2交易序号 + 8000
val processKeyCmd = "${rnd}${trn}8000"
Logger.i("processKey=$processKeyCmd")
//使用发卡时分散的圈存密钥
val sec = calcSecret3(LOAD_SEC)
//3des计算过程密钥
val processKeyRst =
   tdesECB(sec.toBytes(), processKeyCmd.toBytes(), Crypto.DesMode.Encrypt)
Logger.i("processKeyRst结果=${processKeyRst.toHexString()}")

// 计算mac1 4旧余额 4交易金额 1类型标识(01存折02钱包) 6终端机编号
val tMac1Cmd = "$oldBalance${amountHex}02$terminalId"
Logger.i("tmacCmd=$tMac1Cmd")
val calcMac1Rst = pbocDesMac(tMac1Cmd.toBytes(), processKeyRst, defaultIv)
Logger.i("mac1=$mac1 mac11=${calcMac1Rst.toHexString()} ")
//判断初始化时mac与加密完成mac是否相等
if (mac1 != calcMac1Rst.toHexString()) throw  CardException("")
datetimeStr = timestamp.unix2Date("yyyyMMddHHmmss")
//4字节交易金额+1字节交易标识+6字节终端机编号+7字节终端交易日期时间)
val tMac2Cmd = "${amountHex}02$terminalId$datetimeStr"
val calcMac2Rst = pbocDesMac(tMac2Cmd.toBytes(), processKeyRst, defaultIv)
val creditForLoadRst =
   "$creditForLoad$datetimeStr${calcMac2Rst.toHexString()}".cPUOperating()
 返回代码9000 圈存完成      

以上代码包含下面4个步骤

  • 圈存初始化获得mac
  • 3DES计算过程密钥
  • 使用过程密钥对4交易金额 1类型标识(01存折02钱包) 6终端机编号进行pbocMac计算
  • 如果初始化结果得到的mac和使用过程密钥计算的mac相等,则圈存成功

注: psam卡和cpu卡发卡的分散因子必须一致