一、实验题目
带有 PIN 校验功能的钱包交易
二、应用APDU命令
APDU: Application Protocol data unit,,是智能卡与智能卡读卡器之间传送的信息单元, (向智能卡发送的命令)指令(ISO 7816-4规范有定义):CLA INS P1 P2 Lc Data Le。
CLA:指令类别;
INS:指令码;
P1、P2:参数;
Lc:为Data的长度;
Le:为希望响应时回答的数据字节数,0表最大可能长度。
C-APDU:命令APDU组成如下
指令 | CLA | INS | P1 | P2 | Lc | 数据 | Le |
Verify | 80 | 20 | 00 | 00 | Pin值长度 | 010203040506 (本实验的pin值) | —— |
Credit | 80 | 30 | 00 | 00 | 01 | 存款数值 | —— |
Debit | 80 | 40 | 00 | 00 | 01 | 借款数值 | —— |
Get Balance | 80 | 50 | 00 | 00 | 00 | —— | 02 |
R-APDU:响应APDU组成如下
指令 | 说明 | 数据 | SW1 | SW2 |
Verify | 验证成功 | 90 | 00 | |
PIN值错误 | 63 | 00 | ||
Pin值的长度错误(不为6) | 67 | 00 | ||
Credit | 需要验证身份 | 63 | 01 | |
单次存款超出最高限制 | 6A | 83 | ||
存款数值超过最大数值 | 6A | 84 | ||
存款成功 | 90 | 00 | ||
Debit | 需要验证身份 | 63 | 01 | |
单次借款超出最高限制 | 6A | 83 | ||
余额为负值 | 6A | 85 | ||
借款成功 | 90 | 00 | ||
Get Balance | 返回余额数值 | (余额数据) | 90 | 00 |
Le值错误 | 67 | 00 |
注:
本实验设置的是8次错误锁定。
初始时设定PIN验证码为010203040506,身份验证时发送数据010203040506正确验证身份。
存款、借款时,设定单次交易数值不超过0x7F(127)。
余额总量限定为0x7FFF,且余额不能为负值。
三、程序设计说明
(给出程序总体结构、函数和处理流程的说明)
总体结构:
- 设计一个电子钱包小程序,要求至少实现电子钱包安装、选择与撤销选择、存款、借款、获取身钱包余额、身份验证的功能。
- 身份验证可通过PIN码来设置
- 存款、借款、消费可以通过设置一余额变量Balance,通过读取相应操作指令,对变量Balance进行加、减、读取值来实现存款、借款、消费功能。
- 对不符合规定的操作,抛出异常来中断操作。
总体框架图如下:
函数:
-
存款函数
- private void credit(APDU apdu) {
- // access authentication
- if ( ! pin.isValidated() )
- ISOException.throwIt(
- SW_PIN_VERIFICATION_REQUIRED);
- byte[] buffer = apdu.getBuffer();
- // Lc byte denotes the number of bytes in the
- // data field of the command APDU
- byte numBytes = buffer[ISO7816.OFFSET_LC];
- // indicate that this APDU has incoming data
- // and receive data starting from the offset
- // ISO7816.OFFSET_CDATA following the 5 header
- // bytes.
- byte byteRead = (byte)(apdu.setIncomingAndReceive());
- // it is an error if the number of data bytes
- // read does not match the number in Lc byte
- if ( ( numBytes != 1 ) || (byteRead != 1) )
- ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
- // get the credit amount
- byte creditAmount = buffer[ISO7816.OFFSET_CDATA];
- // check the credit amount
- if ( ( creditAmount > MAX_TRANSACTION_AMOUNT)
- || ( creditAmount < 0 ) )
- ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);
- // check the new balance
- if ( (short)( balance + creditAmount) > MAX_BALANCE )
- ISOException.throwIt(SW_EXCEED_MAXIMUM_BALANCE);
- // credit the amount
- balance = (short)(balance + creditAmount);
- } // end of deposit method
存款流程框架图如下:
-
取款函数
- private void debit(APDU apdu) {
- // access authentication
- if ( ! pin.isValidated() )
- ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
- byte[] buffer = apdu.getBuffer();
- byte numBytes = (byte)(buffer[ISO7816.OFFSET_LC]);
- byte byteRead = (byte)(apdu.setIncomingAndReceive());
- if ( ( numBytes != 1 ) || (byteRead != 1) )
- ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
- // get debit amount
- byte debitAmount = buffer[ISO7816.OFFSET_CDATA];
- // check debit amount
- if ( ( debitAmount > MAX_TRANSACTION_AMOUNT)
- || ( debitAmount < 0 ) )
- ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);
- // check the new balance
- if ( (short)( balance - debitAmount ) < (short)0 )
- ISOException.throwIt(SW_NEGATIVE_BALANCE);
- balance = (short) (balance - debitAmount);
- } // end of debit method
总结取款函数流程框架如下:
-
Pin验证函数
- public boolean select() {
- // The applet declines to be selected
- // if the pin is blocked.
- if ( pin.getTriesRemaining() == 0 )
- return false;
- else
- return true;
- }
- public void deselect() {
- // reset the pin value
- pin.reset();
- }
- private void verify(APDU apdu) {
- byte[] buffer = apdu.getBuffer();
- // retrieve the PIN data for validation.
- byte byteRead = (byte)(apdu.setIncomingAndReceive());
- // check pin
- // the PIN data is read into the APDU buffer
- // at the offset ISO7816.OFFSET_CDATA
- // the PIN data length = byteRead
- //判断输入pin值长度是否为6
- if (byteRead != 6) {
- // If the length is not 6, throw SW_WRONG_LENGTH exception
- ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
- }
- if ( pin.check(buffer, ISO7816.OFFSET_CDATA, byteRead) == false )
- /ISOException.throwIt(SW_VERIFICATION_FAILED);
PIN验证流程框架如下图:
-
查询余额函数
- private void getBalance(APDU apdu) {
- byte[] buffer = apdu.getBuffer();
- // inform system that the applet has finished
- // processing the command and the system should
- // now prepare to construct a response APDU
- // which contains data field
- short le = apdu.setOutgoing();
- if ( le < 2 )
- ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
- //informs the CAD the actual number of bytes
- //returned
- apdu.setOutgoingLength((byte)2);
- // move the balance data into the APDU buffer
- // starting at the offset 0
- buffer[0] = (byte)(balance >> 8);
- buffer[1] = (byte)(balance & 0xFF);
- // send the 2-byte balance at the offset
- // 0 in the apdu buffer
- apdu.sendBytes((short)0, (short)2);
- } // end of getBalance method
查询余额流程框架图如下:
- 测试APDU命令集
(给出测试脚本的设计说明)
#选择我们的钱包
/select 112233445501
#验证PIN值
/send 8020000006010203040506
#查询余额
/send 805000000002
#从钱包里取0x64的钱
/send 804000000164
#0x6A85 = SW_NEGATIVE_BALANCE
#往钱包里存0x64的钱
/send 803000000164
#查询余额
/send 805000000002
#0x00 0x64 0x9000 = Balance = 100 and SW_NO_ERROR
#从钱包里取0x32的钱
/send 804000000132
#0x9000 = SW_NO_ERROR
#查询余额
/send 805000000002
#0x00 0x32 0x9000 = Balance = 50 and SW_NO_ERROR
#往钱包里存0x80的钱
/send 803000000180
#0x6A83 = SW_INVALID_TRANSACTION_AMOUNT
#输入错误的pin值
/send 802000000401030266
#0x6300 = SW_VERIFICATION_FAILED
#用错误的le值取款
/send 805000000001
#0x6700 = ISO7816.SW_WRONG_LENGTH
- 测试运行结果
(给出实际运行结果的截图和说明)
#选择钱包,输入钱包的uid值112233445501
#返回9000,表示操作成功
#输入pin值,010203040506
#返回9000,表示pin值正确
#输入错误的pin值
#返回63 00,表示pin值错误
#输入pin值的长度不为0x06
#会抛出异常,返回67 00,表示长度错误
#尝试pin值达到上限(本实验设置的是8次)后,再次输入正确的密码,也会返回false
#进行查询余额的操作
#查到余额是0,返回9000表示操作成功
#进行从余额为0的钱包里取款0x64的操作
#返回6A 85表示取款失败,因为钱包不能为负值
#存款0x64的钱,接着查询钱包余额
#存款成功,钱包里有0x64的钱
#取0x32的钱,接着查询余额
#取款成功,余额还剩0x32
#取0x80的钱
#取款失败,因为之前设置过单次交易的金额不能超过0x7F
#想查看余额
#操作错误,因为get balance的le值是02而不是01