一、需求分析
設計一個電子錢包小程序,要求至少實現電子錢包安裝、選擇與撤銷選擇、存款、借款、獲取身錢包餘額、身份驗證的功能。
身份驗證可通過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;
public class WALLET extends Applet {
final static byte Wallet_CLA = (byte)0x80;
//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((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);
}
*/
}