一、需求分析
设计一个电子钱包小程序,要求至少实现电子钱包安装、选择与撤销选择、存款、借款、获取身钱包余额、身份验证的功能。
身份验证可通过PIN码来设置。
存款、借款、消费可以通过设置一余额变量Balance,通过读取相应操作指令,对变量Balance进行加、减、读取值来实现存款、借款、消费功能。
对不符合规定的操作,抛出异常来中断操作。
二、APDU
C-APDU
CLA | INS | P1 | P2 | Lc | 数据 | Le | |
Verify | 90 | 20 | 00 | 00 | 04 | 01020304 | 00 |
Credit | 90 | 30 | 00 | 00 | 01 | 存款数值 | 00 |
Debit | 90 | 40 | 00 | 00 | 01 | 借款数值 | 00 |
Get Balance | 90 | 50 | 00 | 00 | 00 | —— | 02 |
R-APDU
指令 | 说明 | 数据 | SW1 | SW2 |
Verify | 验证成功 | —— | 90 | 00 |
PIN值错误 | —— | 63 | 00 | |
Credit | 存款成功 | —— | 90 | 00 |
需要验证身份 | —— | 63 | 01 | |
单次存款超出最高限制 | —— | 6A | 83 | |
存款数值超过最大数值 | —— | 6A | 84 | |
Debit | 借款成功 | —— | 90 | 00 |
需要身份验证 | —— | 63 | 01 | |
单次借款超出最高限制 | —— | 6A | 83 | |
余额为负值 | —— | 6A | 85 | |
Get Balance | 返回余额数值 | (余额数据) | 90 | 00 |
注:
- 初始时设定PIN验证码为01020304,身份验证时发送数据01020304正确验证身份。
- 存款、借款时,设定单次交易数值不超过0x7F。
- 余额总量限定为0x7530,且余额不能为负值。
三、测试
采用源码为《JAVA智能卡原理与应用开发》一书中示例
- 电子钱包安装与选择:
电子钱包安装选择成功。
- 未验证身份就存款:
抛出异常:0x6301,pin码身份验证未通过。
- 身份验证时输入错误PIN码:
抛出异常:0x6300,身份验证失败。
- 身份验证时输入正确PIN码:
身份验证成功。
- 查询此时余额:
余额此时为0。
- 单次存款超0x7F:
抛出异常:0x6A83,超出单次交易额最大值。
- 查询此时余额:
余额仍为0。
- 存款,金额0x70,并查询余额:
余额为存款金额0x70 = 112。
- 借款,金额大于余额0x70,并查询余额:
抛出异常:0x6A85,无效余额,借款失败;
余额不变,仍为0x70。
- 借款,金额0x20,并查询余额:
借款成功,余额0x50。
- 存款,金额0x75,并查询余额:
存款成功,余额0xC5
- 存款,金额0x70,并查询余额:
存款成功,余额0x0135
四、源代码
package wallet;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.APDU;
import javacard.framework.*;
/**
* @author JiFengZ
*
*/
public class WALLET extends Applet {
final static byte Wallet_CLA = (byte)0x90;
final static byte VERIFY = (byte)0x20;
final static byte CREDIT = (byte)0x30;
final static byte DEBIT = (byte)0x40;
final static byte GET_BALANCE = (byte)0x50;
final static short MAX_BALANCE = 0x7530;
final static byte MAX_TRANSACTION_AMOUNT = 127;
final static byte PIN_TRY_LIMIT = (byte)0x03;
final static byte MAX_PIN_SIZE = (byte)0x08;
final static short SW_VERIFICATION_FAILED = 0x6300;
final static short SW_PIN_VERIFICATION_REQUIRED = 0x6301;
final static short SW_INVALID_TRANSACTION_AMOOUNT = 0x6A83;
final static short SW_EXCEED_MAXIMUM_BALANCE = 0x6A84;
final static short SW_NEGATIVE_BALANCE = 0x6A85;
OwnerPIN pin;
short balance;
/*
private WALLET(byte[] bArray, short bOffset, byte bLength)
{
pin = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
byte iLen = bArray[bOffset];
bOffset = (short)(bOffset + iLen + 1);
byte cLen = bArray[bOffset];
bOffset = (short)(bOffset + cLen + 1);
byte aLen = bArray[bOffset];
pin.update(bArray, (short)(bOffset + 1), aLen);
register();
}
*/
public static void install(byte[] bArray, short bOffset, byte bLength) {
// GP-compliant JavaCard applet registration
new WALLET().register(bArray, (short) (bOffset + 1), bArray[bOffset]);
//new WALLET(bArray, bOffset, bLength);
}
public boolean select()
{
if(pin.getTriesRemaining() == 0)
return false;
return true;
}
public void deselect()
{
pin.reset();
}
public void process(APDU apdu)
{
/*
// Good practice: Return 9000 on SELECT
if (selectingApplet()) {
return;
}
*/
byte[] buffer = apdu.getBuffer();
buffer[ISO7816.OFFSET_CLA] = (byte)(buffer[ISO7816.OFFSET_CLA] & (byte)0xFC);
if((buffer[ISO7816.OFFSET_CLA] == 0) &&
(buffer[ISO7816.OFFSET_INS] == (byte)(0xA4)))
return;
if(buffer[ISO7816.OFFSET_CLA] != Wallet_CLA)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
switch (buffer[ISO7816.OFFSET_INS]) {
case GET_BALANCE:
getBalance(apdu);
return;
case DEBIT:
debit(apdu);
return;
case CREDIT:
credit(apdu);
return;
case VERIFY:
verify(apdu);
return;
default:
// good practice: If you don't know the INStruction, say so:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
private void credit(APDU apdu)
{
if(!pin.isValidated())
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
byte[] buffer = apdu.getBuffer();
byte numBytes = buffer[ISO7816.OFFSET_LC];
byte byteRead = (byte)(apdu.setIncomingAndReceive());
if((numBytes != 1) || (byteRead != 1))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
byte creditAmount = buffer[ISO7816.OFFSET_CDATA];
if((creditAmount > MAX_TRANSACTION_AMOUNT) || (creditAmount < 0))
ISOException.throwIt(SW_INVALID_TRANSACTION_AMOOUNT);
if((short)(balance + creditAmount) > MAX_BALANCE)
ISOException.throwIt(SW_EXCEED_MAXIMUM_BALANCE);
balance = (short)(balance + creditAmount);
}
private void debit(APDU apdu)
{
if(!pin.isValidated())
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
byte[] buffer = apdu.getBuffer();
byte numBytes = buffer[ISO7816.OFFSET_LC];
byte byteRead = (byte)(apdu.setIncomingAndReceive());
if((numBytes != 1) || (byteRead != 1))
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
byte debitAmount = buffer[ISO7816.OFFSET_CDATA];
if((debitAmount > MAX_TRANSACTION_AMOUNT) || (debitAmount < 0))
ISOException.throwIt(SW_INVALID_TRANSACTION_AMOOUNT);
if((short)(balance - debitAmount) < (short)0)
ISOException.throwIt(SW_NEGATIVE_BALANCE);
balance = (short)(balance - debitAmount);
}
private void getBalance(APDU apdu)
{
byte[] buffer = apdu.getBuffer();
short le = apdu.setOutgoing();
if(le < 2)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
apdu.setOutgoingLength((byte)2);
buffer[0] = (byte)(balance >> 8);
buffer[1] = (byte)(balance & 0xFF);
apdu.sendBytes((short)0, (short)2);
}
private void verify(APDU apdu)
{
byte[] buffer = apdu.getBuffer();
byte byteRead = (byte)(apdu.setIncomingAndReceive());
if(pin.check(buffer, ISO7816.OFFSET_CDATA, byteRead) == false)
ISOException.throwIt(SW_VERIFICATION_FAILED);
}
}