实验三:带有 PIN 校验功能的钱包交易

  • Author:ZERO-A-ONE
  • Date:2021-04-29

一、实验目的

掌握目前通用的使用个人密码进行校验的电子钱包应用的开发

二、实验环境

Eclipse 集成开发环境,Java Key,JCOP仿真运行环境

三、实验原理

按照 Applet 建立和执行过程中方法被 JCRE调用的顺序,分别为 install、register、select、process 和 deselect 方法。install、select、deselect 和 process 方法都是 JCRE 入口点方法,将由 JCRE 负责调用。同时 Applet 类只实现了这些方法的默认功能,如果 Applet 开发者想要增加其他功能或对其修改,可以通过重载 Applet 部分或全部方法来实现。由于以上几个方法是 Applet 开发过程中必须用到的

在本次实验中,要求我们掌握编写一个基本Java智能卡应用的方法。这里我们涉及的内容主要包括process方法、deselect方法的重载

当Applet被选择后,由终端发送的命令将会交付Applet.process()方法执行。Applet.process()方法为虚方法,每一个Applet必须要重载该方法,在方法中定义自身的命令执行流程,由此来完成应用和同终端的命令交互过程。通常情况下,process()方法将会首先检查输入命令结构是否正确,然后根据INS进入不同的处理流程,最后返回执行结果给终端

process()方法执行中,Applet会主动抛出ISOException,该异常将会被JCRE自动捕获,并将ISOException.Reason作为状态字返回给终端。对于其他异常,JCRE不能自动捕获。若Applet没有自行捕获,JCRE将会返回ISO7816.SW_UNKNOWN,即0x6F00给终端,表示应用执行过程中出现了未知异常

四、程序逻辑

4.1 install

首先程序运行时会去执行install方法,该方法由 JCRE 调用,用于创建一个 Applet 对象实例。在方法执行过程中,首先将创建 Applet所需对象;然后进行初始化操作,为数据赋初值;最后调用 register 方法将 Applet 注册到Java 智能卡平台中。只有成功执行了 register 方法,Applet install 方法才算执行成功。

private Wallet(byte[] bArray, short bOffset, byte bLength) {
	// Wallet 应用构造函数,推荐将应用所需的所有变量,
	// 统一放到构造函数内进行分配,这样将减少内存泄漏的可能
	byte pinInitValue[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
	// 以给定的参数创建 OwnerPIN 对象实例 PIN 
	pin = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
	pin.update(pinInitValue, (short) 0, (byte) 6);
	register();
}

public static void install(byte[] bArray, short bOffset, byte bLength) {
	new Wallet(bArray, bOffset, bLength);
}

4.2 register

register 方法负责将 Applet 实例注册到 JCRE 中,并为之分配相应的 AID,该 AID 和Applet 一一对应,JCRE 可以通过此 AID 选择对应的 Applet

4.3 select/deselect

Applet 在被选中之前,一直处在挂起状态,当 JCRE 收到一个 SELECT 命令,且该SELECT 命令的数据段和某个 Applet 的实例化 AID 相一致时,该应用将被选中,JCRE 将会调用 Applet 的 select 方法

public boolean select() {
	// The applet declines to be selected
	// if the pin is blocked.
	if (pin.getTriesRemaining() == 0)
		return false;
	else
		return true;
}

当未被选中时会执行deselect

public void deselect() {
	// reset the pin value
	pin.reset();
}

4.4 process

当 Applet 被选择后,由终端发送的命令将会交付Applet.process()方法执行。Applet.process()方法为虚方法,每一个 Applet 必须要重载该方法,在方法中定义自身的命令执行流程,由此来完成应用和同终端的命令交互过程。通常情况下,process()方法将会首先检查输入命令结构是否正确,然后根据 INS 进入不同的处理流程,最后返回执行结果给终端

public void process(APDU apdu) {
	// APDU 对象为 JCRE 临时入口点对象,它可被任何应用所访问,
	// 负责传递终端发送的 APDU 命令。通过 APDU.getBuffer()命令,
	// 即可以得到 APDU 对象的通信缓冲区,即 APDU 命令数组   
	byte[] buffer = apdu.getBuffer();
	// 判断命令头是否正确
	buffer[ISO7816.OFFSET_CLA] = (byte) (buffer[ISO7816.OFFSET_CLA] & (byte) 0xFC);
	// 若为 SELECT 命令,则直接返回,不做其他操作
	if ((buffer[ISO7816.OFFSET_CLA] == 0) && (buffer[ISO7816.OFFSET_INS] == (byte) (0xA4)))
	return;
	// 若为其他命令,则判断命令 CLA 和 INS 是否能够为 Wallet 应用所支持,
	// 若为支持范围外的其他值,则返回对象的错误状态字
	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:
            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
	}
}

可以看到在这里通过判断buffer[ISO7816.OFFSET_INS]的值去选择不同的处理功能函数

根据前面常量的定义可以知道不同的INS代表的功能函数

// Wallet 应用支持的 INS
// codes of INS byte in the command APDU header
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;

Wallet 应用定义了 4 条命令,分别为校验 PIN、加钱、减钱和读余额。其中,加钱、减钱和读余额都是对钱包余额的操作。PIN 的引入增加了钱包应用的安全性,Wallet 应用要求在进行交易(加钱或减钱)之前,必须进行 PIN 校验,只有 PIN 校验成功后,才能对余额数据进行修改。同时在取消选择应用时,重载了 deselect 方法将 PIN 的校验结果清空,从而要求在每次选择应用后都需进行 PIN 验证,避免了安全漏洞的产生

4.5 getBalance

这里主要实现了读余额的功能

private void getBalance(APDU apdu) {
	byte[] buffer = apdu.getBuffer();
	// 设置通信传输方向为卡片到终端,同时也表示卡片运行结束,
	// 准备发送命令响应给终端,其中 le 为 APDU 命令中的 LE,
	// 表示终端要求卡片返回的响应数据的长度
	short le = apdu.setOutgoing();

	if (le < 2)
		ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

	// 设置卡片发送数据的实际长度
	apdu.setOutgoingLength((byte) 2);

	// 复制余额数据到 APDU 缓冲区中,准备发送给终端
	buffer[0] = (byte) (balance >> 8);
	buffer[1] = (byte) (balance & 0xFF);

	// 调用通信函数发送余额数据
	apdu.sendBytes((short) 0, (short) 2);
} 

4.6 credit

这里主要实现了加钱的功能

private void credit(APDU apdu) {
	// 钱包应用鉴权
	if (!pin.isValidated())
		ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);

	byte[] buffer = apdu.getBuffer();

	// 取命令 LC,并将之存储在 numBytes 中
	byte numBytes = buffer[ISO7816.OFFSET_LC];

	// 接收 APDU 命令数据,并将之存储在 APDU 通信缓冲区的
	// ISO7816.OFFSET_CDATA 处,接着 5 字节的 APDU 命令头
	byte byteRead = (byte) (apdu.setIncomingAndReceive());

	// 判断 LC 是否为 1,否则抛出异常。本应用只支持一个字节长度的存钱交易
	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_AMOUNT);

	// 判断若进行存钱交易,余额是否超出允许最大值
	if ((short) (balance + creditAmount) > MAX_BALANCE)
		ISOException.throwIt(SW_EXCEED_MAXIMUM_BALANCE);

	// 若以上条件全部满足,则更新钱包余额
	balance = (short) (balance + creditAmount);

} // end of deposit method

4.7 debit

这里主要实现了减钱的功能

private void debit(APDU apdu) {
	// 钱包应用鉴权
	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);

       // 取即将消费的值
        byte debitAmount = buffer[ISO7816.OFFSET_CDATA];

        // 判断消费交易额度是否满足要求
        if ((debitAmount > MAX_TRANSACTION_AMOUNT) || (debitAmount < 0))
            ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);

        // 判断钱包余额是否大于消费的数额,即钱包是否透支
        if ((short) (balance - debitAmount) < (short) 0)
            ISOException.throwIt(SW_NEGATIVE_BALANCE);
        // 若以上条件全部满足,最后更改钱包余额
        balance = (short) (balance - debitAmount);

    } // end of debit method

五、总的工程

package wallet;

import javacard.framework.*;

public class Wallet extends Applet {
    /* 常量声明 */ 
    //Wallet 应用支持的 CLA 
    // code of CLA byte in the command APDU header
    final static byte Wallet_CLA = (byte) 0x80;
    // Wallet 应用支持的 INS
    // codes of INS byte in the command APDU header
    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 = 0x7FFF;
    // 交易最大值
    final static byte MAX_TRANSACTION_AMOUNT = 127;

    // PIN 锁定前允许的最大错误尝试次数
    final static byte PIN_TRY_LIMIT = (byte) 0x03;
    // PIN 值的最大长度
    final static byte MAX_PIN_SIZE = (byte) 0x08;

    /* Wallet 应用返回的错误码 */ 
    // 表明 PIN 认证错误
    final static short SW_VERIFICATION_FAILED = 0x6300;

    // 表明在交易进行之前需要先进行 PIN 认证
    final static short SW_PIN_VERIFICATION_REQUIRED = 0x6301;
    // 表明交易额小于零或大于最大允许值
    // amount > MAX_TRANSACTION_AMOUNT or amount < 0 
    final static short SW_INVALID_TRANSACTION_AMOUNT = 0x6A83;

    // 表明余额已超出最大允许值
    final static short SW_EXCEED_MAXIMUM_BALANCE = 0x6A84;
    // 表明余额为负值
    final static short SW_NEGATIVE_BALANCE = 0x6A85;

    /* 实例化变量声明 */ 
    // 钱包应用 PIN 值
    OwnerPIN pin;
    short balance;

    private Wallet(byte[] bArray, short bOffset, byte bLength) {
        // Wallet 应用构造函数,推荐将应用所需的所有变量,
        // 统一放到构造函数内进行分配,这样将减少内存泄漏的可能
        byte pinInitValue[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
        // 以给定的参数创建 OwnerPIN 对象实例 PIN 
        pin = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
        pin.update(pinInitValue, (short) 0, (byte) 6);
        register();
    }

    public static void install(byte[] bArray, short bOffset, byte bLength) {

        new Wallet(bArray, bOffset, bLength);
    }

    public boolean select() {

        // 在选择钱包应用前,对 PIN 可尝试次数进行判断,若可尝试次数为零,
        // 即钱包已被锁定,则该钱包应用不能够被选择
        if (pin.getTriesRemaining() == 0)
            return false;
        else
            return true;

    }

    public void deselect() {

        // reset the pin value
        pin.reset();

    }

    public void process(APDU apdu) {
        // APDU 对象为 JCRE 临时入口点对象,它可被任何应用所访问,
        // 负责传递终端发送的 APDU 命令。通过 APDU.getBuffer()命令,
        // 即可以得到 APDU 对象的通信缓冲区,即 APDU 命令数组   
        byte[] buffer = apdu.getBuffer();
        // 判断命令头是否正确
        buffer[ISO7816.OFFSET_CLA] = (byte) (buffer[ISO7816.OFFSET_CLA] & (byte) 0xFC);
        // 若为 SELECT 命令,则直接返回,不做其他操作
        if ((buffer[ISO7816.OFFSET_CLA] == 0) && (buffer[ISO7816.OFFSET_INS] == (byte) (0xA4)))
            return;
        // 若为其他命令,则判断命令 CLA 和 INS 是否能够为 Wallet 应用所支持,
        // 若为支持范围外的其他值,则返回对象的错误状态字
        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:
            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }

    } // end of process method

    private void credit(APDU apdu) {

        // 钱包应用鉴权
        if (!pin.isValidated())
            ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);

        byte[] buffer = apdu.getBuffer();

        // 取命令 LC,并将之存储在 numBytes 中
        byte numBytes = buffer[ISO7816.OFFSET_LC];

        // 接收 APDU 命令数据,并将之存储在 APDU 通信缓冲区的
        // ISO7816.OFFSET_CDATA 处,接着 5 字节的 APDU 命令头
        byte byteRead = (byte) (apdu.setIncomingAndReceive());

        // 判断 LC 是否为 1,否则抛出异常。本应用只支持一个字节长度的存钱交易
        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_AMOUNT);

        // 判断若进行存钱交易,余额是否超出允许最大值
        if ((short) (balance + creditAmount) > MAX_BALANCE)
            ISOException.throwIt(SW_EXCEED_MAXIMUM_BALANCE);

        // 若以上条件全部满足,则更新钱包余额
        balance = (short) (balance + creditAmount);

    } // end of deposit method

    private void debit(APDU apdu) {

        // 钱包应用鉴权
        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);

       // 取即将消费的值
        byte debitAmount = buffer[ISO7816.OFFSET_CDATA];

        // 判断消费交易额度是否满足要求
        if ((debitAmount > MAX_TRANSACTION_AMOUNT) || (debitAmount < 0))
            ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);

        // 判断钱包余额是否大于消费的数额,即钱包是否透支
        if ((short) (balance - debitAmount) < (short) 0)
            ISOException.throwIt(SW_NEGATIVE_BALANCE);
        // 若以上条件全部满足,最后更改钱包余额
        balance = (short) (balance - debitAmount);

    } // end of debit method

    private void getBalance(APDU apdu) {

        byte[] buffer = apdu.getBuffer();

        // 设置通信传输方向为卡片到终端,同时也表示卡片运行结束,
        // 准备发送命令响应给终端,其中 le 为 APDU 命令中的 LE,
        // 表示终端要求卡片返回的响应数据的长度
        short le = apdu.setOutgoing();

        if (le < 2)
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

        // 设置卡片发送数据的实际长度
        apdu.setOutgoingLength((byte) 2);

        // 复制余额数据到 APDU 缓冲区中,准备发送给终端
        buffer[0] = (byte) (balance >> 8);
        buffer[1] = (byte) (balance & 0xFF);

        // 调用通信函数发送余额数据
        apdu.sendBytes((short) 0, (short) 2);

    } // end of getBalance method

    private void verify(APDU apdu) {

        byte[] buffer = apdu.getBuffer();
        // 接收终端发送的 PIN 数据,并将之存储到 APDU 通信缓冲区中

        byte byteRead = (byte) (apdu.setIncomingAndReceive());

        // 判断数终端数据的 PIN 是否和卡片内的 PIN 值相符
        if (pin.check(buffer, ISO7816.OFFSET_CDATA, byteRead) == false)
            ISOException.throwIt(SW_VERIFICATION_FAILED);

    } 

}

这里需要注意AID已经改变,和之前的AID不一样,注意看卡片信息

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值