Chinaunix首页 | 论坛 | 博客
  • 博客访问: 754673
  • 博文数量: 98
  • 博客积分: 4934
  • 博客等级: 上校
  • 技术积分: 1151
  • 用 户 组: 普通用户
  • 注册时间: 2009-06-12 19:55
文章分类

全部博文(98)

文章存档

2014年(1)

2013年(2)

2012年(4)

2011年(25)

2010年(33)

2009年(33)

分类: 嵌入式

2010-07-30 16:25:27

标签: 无标签

2003 年 3 月 01 日

简介:在本系列的前两篇文章,我们讨论了智能卡与JAVA 卡的相关知识,此篇文章将着重介绍 JAVA 卡的 Applet一些基础知识,首先我们介绍了 JAVA 卡 Applet的开发环境和开发过程,并着重介绍一下 JAVA 卡 Applet的编译和转换过程。随后我们通过一个“电子钱包”例子的引入,进一步了解JAVA 卡 Applet开发的一些细节过程。为了便于介绍,在这篇文章中,我们只介绍在Windows 操作平台上的 Applet 的开发。

希望你在阅读这篇文章时,已经有了初步的 JAVA 语言的基础,并且已经阅读了本系列的前两篇篇文章:《 智能卡与安全》和 《 JAVA 卡概述

硬件:PC(奔腾II 266 以上),读卡器,JAVA 卡

软件:Windows95/98/NT4.0, VJ++6.0, JDK1.2.2, Java Card 2.1.1 Development Kit

在上述软件清单中,Java Card 2.1.1 Development Kit(JAVA 卡开发工具)是开发 JAVA 智能卡 Applet 所特有的工具。我们在这对它进行简单的介绍。你能在 网址得到这个 JAVA 卡开发工具。 Java Card 2.1.1 Development Kit 主要包括了:

  1. 应用工具:如转换器(converter),apdutools等。它们位于 bin 目录下。
  2. 文档: Java Card? 2.1.1 Development Kit User’s Guide(JAVA 卡开发工具用户指南),位于 doc 目录下
  3. JAVA 卡编译时必须的 exp 和 class 文件。exp 文件位于 api21 目录的子目录下,它们分别是 framework.exp, security.exp, lang.exp 和 crypto.exp。class 文件主要存放在 api21.jar 文件中,位于 bin 目录下
  4. 例子:一些有用的例子位于 sample 目录下


点击看大图




回页首


在介绍 JAVA 卡 Applet 生成过程之前,我们先来回顾一些 JAVA 智能卡的术语:

APDU(应用协议数据单元):用于智能卡与外界进行数据交换的基本命令单位。一个 APDU 要么包含一个指令消息,要么包含一个响应消息,这个消息发自智能卡或者读卡设备。它是智能卡与外界通信的基础。详细信息参照 ISO 7816-3 标准。

EEPROM (Electrically-Erasable Programmable Read-Only Memory):一种出厂后还能被写入数据的存储器

AID(Application identifier 应用ID号):ISO 7816-5 定义了 AID 的结构,为了能使每一个 Applet 都有一个唯一的ID身份号。JAVA 卡通过 AID 来确认 Applet。AID 是由ISO国际组织来管理的,所以它是唯一的。

JCRE(Java Card Runtime Environment JAVA 卡运行环境):它包括 JAVA 虚拟机,JAVA 卡的框架(Framework) 和一些基本函数(native functions)。

一些与生成 JAVA 卡 Applet 有关的重要的文件:

  • *.java     JAVA 语言的源代码文件
  • *.class    JAVA 语言的字节代码(Bytecode),它是由 JAVA 编译器 javac 编译产生的二进制代码,由 JAVA 解释器执行
  • *.jca     JAVA 卡的可读汇编语言,由转换器生成,并可进一步生成 Cap 文件
  • *.exp     输出文件,包含 JAVA 卡包(packages)的公共信息和连接信息。
  • *.cap     转化以后的 Applet 文件,它就是可以被 JAVA 卡装载并安装的可执行代码。
  • *.jar     JAVA 文档文件,JAR 文件是一个与平台无关的,包含多个文件的文件格式。

JAVA 卡的 Applet 生成大致可分为以下几个步骤:

  1. JAVA 源代码编辑:开发 JAVA 卡 Applet 首先当然要编辑 JAVA 源代码,开发人员可以任意选用他所喜爱的编辑工具,如UltraEdit, VJ++, Visual Café 等等。
  2. JAVA 源代码编译:开发人员编写好 JAVA 源代码后,就可以用 JAVA 编译器生成 class 文件,当然这一过程还要包括必要的代码调试过程。这一过程需要使用 JAVA 编译器。
  3. Cap 文件生成:JAVA 卡的运行环境并不“认识” JAVA 的 class 文件,为了使开发好的 Applet 能在 JAVA 卡中运行,包含 Applet 的 class 文件必须被转换成 Cap 文件。Cap 文件就是可以被装载到 JAVA 卡上的 Applet。这一过程需要使用 JAVA 卡转换器(converter)。
  4. Applet 的安装:当 Applet 的 Cap 文件生成后,我们就要进行 Applet 的安装。在这一过程中一般我们需要使用 PC 机,读卡器和控制读卡器的软件工具。我们通过使用在 PC 机上的控制读卡器的软件来控制读卡器,从而把 Cap 文件装载到 JAVA 卡上。


点击看大图

在 JAVA 卡的开发过程中,编译和转换过程是两个非常关键的过程,现在我们来介绍一下这两个过程。在介绍之前,我们先来了解一下编译和转换时,一些文件在硬盘上存放的结构:

如下图所示。我们有三层目录:根目录,项目目录和 JavaCard 目录。根目录是存放所有项目目录的目录。项目目录存放着 JAVA 卡源代码文件和编译后的 class 文件。JavaCard 目录是由转换器自动生成的,它存放的是转换器的输出文件:Cap文件,Exp 文件和 Jca 文件。一般上述这些文件的文件名需与项目目录的目录名一致,正常情况下,我们就用项目名来命名项目目录和这些文件名,如下图所示:



  • 编译(compiler)
    编译过程与普通的 JAVA 编译过程没什么区别,它将编译 JAVA 文件,产生一些 class 文件。编译器来自于 Sun 公司的 Java Development Kit (Java 开发工具)。它是一个执行程序“javac.exe”,位于 JDK 的 bin 目录下。编译时,输入文件是:Java 源代码程序(*.java)和一些 JAVA 卡的包(package)。这些包保存在 api21.jar 文件中。api21.jar 位于Java Card Development Kit 的 bin 目录下。输出文件是:class 文件,它的前缀与输入文件一致,后缀为.class ,它将位于 Java 源文件(*.java)同一目录下。

    编译命令行:
    <编译执行程序的路径和程序名> <可选参数>

    相信大家对 JAVA 编译命令行都较为熟悉了,不过请注意在 JAVA 卡 Applet 编译时我们只用“-g”参数,而不用“-O”参数,因为用“-g”参数时,我们能在 class 文件中产生“LocalVariableTable”属性,而这个属性在转换时(converter)要被使用到。但如果同时又使用了“-O”参数,“LocalVariableTable”属性就不会被产生了。这样转换时就会出错。另外 –d 参数指明生成的 class 文件放置的根目录,注意它是根目录,也就是说 class 文件位于此根目录下的项目目录中。

    例如:
    c:\JDK\bin\javac.exe –g –d c:\sample\ -classpath c:\jc211\bin\api21.jar c:\sample\Helloworld\Helloworld.java

    如上命令行即完成了 HelloWorld.java 的编译,同时在编译时用到了 api21.jar 中的一些 class 文件

  • 转换(converter)

    转换器(converter)是由 Java Card Development kit 提供的字节代码工具。作为字节代码工具,它需要 Java 解释器的帮助才能运行。它将 class 文件转换成一些输出文件。转换时,输入文件是:由编译器生成的 class 文件。输出文件是:Cap 文件,Export 文件,JCA 文件,它们的后缀分别是:*.cap,*.exp,*.jca,文件名与输入文件一致。它们将位于 Java 卡项目目录下的一个叫 Javacard 的子目录中。

    转换器命令行:
    <解释器> <-classpath> <被执行的 class> <可选参数> <包(package)名> <包 AID> <版本>

    解释器:提供解释器的路径和文件名,如c:\JDK\bin\java.exe

    被执行的 class :就是位于 converter.jar 中 com\sun\javacard\converter\converter\ 目录下的一些类。

    在安装了 Java Card Development kit(Java 卡开发工具) 后,开发工具会提供给你一个批处理文件:converter.bat,它包含的就是命令行中:<解释器> <-classpath> <被执行的 class> 这三部分内容。也就是说,你在进行 Applet 转换时,对这三部分参数的可以不十分了解,而直接使用 converter.bat 进行文件转换。

    命令行 的一些可选参数的介绍:

    -classdir:项目的根目录
    -exportpath:一些转换时要用到的 Exp 文件的父目录,
    -d:输出的路径,它指明的是根目录
    -applet [AID][classname]:指明缺省 Applet 的AID, 和含 Install() 方法的 class 文件名
    -out [CAP][EXP][JCA]:说明要转换器生成什么文件,一般默认为生成 CAP 和 EXP 文件
    -nobanner:信息使用标准输出
    包(package)名:要被转换的包名
    包 AID:5 到 16 十进制,十六进制或八进制数,表明 Applet 的 AID
    版本:用户自定义的版本号

    例如
    c:\JDK\bin\java.exe –classpath c:\jc211\bin\converter.jar com.sun.javacard.converter.Converter -out EXP JCA CAP -exportpath c:\jc211\api21 -applet 0xa0:0x0:0x0:0x0:0x62:0x3:0x1:0xc:0x1:0x1 com.sun.javacard.samples.HelloWorld.HelloWorld com.sun.javacard.samples.HelloWorld 0xa0:0x0:0x0:0x0:0x62:0x3:0x1:0xc:0x1 1.0

    或 converter.bat -out EXP JCA CAP -exportpath c:\jc211\api21 -applet 0xa0:0x0:0x0:0x0:0x62:0x3:0x1:0xc:0x1:0x1 com.sun.javacard.samples.HelloWorld.HelloWorld com.sun.javacard.samples.HelloWorld 0xa0:0x0:0x0:0x0:0x62:0x3:0x1:0xc:0x1 1.0





回页首


JAVA 卡 Applet 的开发过程与其他软件的开发过程是完全一样的,必须进行设计,实现,测试等过程。在这里我们简单介绍一下 JAVA 卡 Applet 的设计过程,并通过一个简单的例子的引入,进行每个过程的说明。

根据 JAVA 卡 Applet 的特点,一般它的设计过程有以下四个步骤:

  1. Applet 功能定义

    功能定义是确定我们将要完成的 Applet 的功能,即定义 Applet 能做什么,而不能做什么。那我们这个 Applet 的功能是什么呢?

    我们的例子是一个简单的“电子钱包”,它支持存款(credit),取款(debit), 和检查存款余额(get balance)等功能。当然,为了防治非法对“电子钱包”进行操作,这个简单的例子也包含了一些安全保护措施。它要求在使用此 “电子钱包”的某些功能前,如存款,取款等功能,用户的 PIN 码必须被验证。如果用户的 PIN 码连续三次验证都是错的,那么这个“电子钱包”就不能再被使用了。当然,真正的“电子钱包”的安全措施要复杂得多。同时,为了简化“电子钱包”存储过程,我们规定“电子钱包”的最大存储额为 32767(0X7FFF),每次的存取金额最多为 127(0X7F)。

  2. 获取 Applet 的 AID
    在 JAVA 卡的技术中,我们用 AID 来识别 Applet。每个商用 Applet 都有自己唯一的 AID 。ISO7816 根据如下规定来保证 AID 的唯一:
    点击看大图

    其中 RID 是 ISO 分配给各个卡供应商的 ID 号,它们是唯一的。而 PIX 是由各个供应商自己来管理的 Applet 的 ID 号。AID 将会在转换过程中被使用。请参阅上文。当然,读者在编写自己的 Applet 时,AID 只需符合 ISO7816 的长度规定,而无须向 ISO 申请 RID 号。

  3. 设计 Applet 程序的类结构

    JAVA 卡 Applet 必须从 javacard.framework.Applet 类扩展而来,并需要实现一些必须的方法。下面就是这些必须实现的方法,当 JAVA 卡收到终端发出的 APDU 命令后,这些方法就将被调用:

    select ()

    当 Applet 收到“Select”的 APDU 命令, 相对应的 Applet 的 select () 方法将被调用,在 select () 方法中,我们做一些初始化的工作。当 select () 方法返回 true, 则说明被选择的 Applet 已被选择,并准备好处理 APDU 命令。

    deselect ()

    当另一个 Applet 被选中,当前选中的 Applet 的 deselect () 方法将被调用,在 deselect () 方法中一般执行一些复位的工作,在本例子中,我们复位 PIN 码。

    install (byte[] bArray, short bOffset, byte bLength)

    Applet 必须用 install() 方法来创建一个 Applet 的实例,同时调用 register () 方法来注册这个实例

    process (APDU apdu)

    一旦 Applet 被选中,当 Applet 收到 APDU 指令时,process() 方法会被调用。在 process() 方法中,我们将分析 APDU 指令,从而进行相应处理,并返回相应的返回值。

    register ()

    调用register () 方法用来注册 Applet 的实例

    还有一些经常被使用的方法:

    getShareableInterfaceObject (AID client AID, byte parameter)
    getShareableInterfaceObject() 方法是用来实现 Applet 之间相互通信的方法。

    selectingApplet ()
    由于当 Applet 被选中后,所有的 APDU 命令,包括 select 命令都会发送至 process() 方法, selectingApplet () 方法就是为了区别 select 命令与其他命令。一般 selectingApplet () 由 process() 方法调用,返回 true 说明是 select 命令。

  4. 制定 Applet 与终端之间的接口
    为了实现 Applet 与终端之间的通信,我们就要制定它们之间的通信协议。根据 ISO7816 规定,终端与卡之间的通信遵循 APDU 的通信准则,即终端发出 APDU 命令,智能卡执行完 这个 APDU 命令后,返回 APDU 响应。有关 APDU 通信的详细资料请参阅本系列的第一篇文章《智能卡与安全》。然而,虽然 ISO7816 制定了智能卡与终端的通信准则,但它并没有制定通信的具体内容。所以为了实现智能卡与终端的通信,为了使智能卡与终端能够彼此“听懂”对方的“语言”,我们必须在 APDU 通信的基础上人为制定一些双方明白的“语言”。下面我们例举了本例子的一些通信接口“语言”:

    Verify 指令

    Verify APDU 指令 (验证 PIN 码指令 )- 终端发给卡

    CLA

    INS

    P1

    P2

    Lc

    Data Field

    Le

    0xB0

    0x20

    0x0

    0x0

    PIN 码的长度

    PIN 码

    • CLA 指明我们的 Applet 的指令集是“B0”
    • INS 指明“Verify” 指令是“20”,当终端发出“20”的指令给 Applet,Applet 就知道它是“Verify”命令( 验证 PIN 码命令)
    • P1,P2 在这没有用,我们设置为0
    • Lc 指明 Data Field 的长度,在这里就是 PIN 码的长度
    • Data Field 在这里就是 PIN 码的值
    • Le 没有用
    Verify APDU 响应 (验证 PIN 码指令响应)- 卡发给终端
    Data Field SW1 + SW2 (状态字) 状态字含义
    0x9000 命令执行成功
    0x6300 验证 PIN 码失败

    Data Field 在这里没有用

    CREDIT 指令

    CREDIT APDU 指令(存款指令)- 终端发给卡
    CLA INS P1 P2 Lc Data Field Le
    0xB0 0x30 0x0 0x0 1 存款金额

    为了方便介绍,我们规定每次的存取金额最多为 127(0X7F),所以存款金额(Data Field)用一个字节就能表示,而 Lc 一定为1

    CREDIT APDU 响应 (存款指令响应)- 卡发给终端
    Data Field SW1 + SW2 (状态字) 状态字含义
    0x9000 存款命令执行成功
    0x6A83 存款金额无效
    0x6A84 存储总金额超过最大值
    0x6301 存款前需验证 PIN 码

    GetBalance 指令

    GetBalance APDU 指令(余额查询指令)- 终端发给卡
    CLA INS P1 P2 Lc Data Field Le
    0xB0 0x50 0x0 0x0 2

    为了方便介绍,我们规定这张“电子钱包”卡的最大存储金额为 32767(0X7FFF),所以我们用两个字节就能得到存储金额的余额,所以 Le 为 2

    GetBalance APDU 响应 (余额查询指令响应)- 卡发给终端
    Data Field SW1 + SW2 (状态字) 状态字含义
    存款余额 0x9000 余额查询命令执行成功

    Data Field 即存款余额

除上面我们所提到的状态字(SW1+SW2),javacard.framework.ISO7816 中已定义了很多状态字可供我们使用,如 ISO7816.SW_INS_NOT_SUPPORTED 表明指令未知。我们可以直接使用这些状态字。

通过上文,我们已经知道,在 Applet 被选择(select)后,当 JAVA 卡收到 APDU 指令后,process() 方法会被调用,所有的有关 APDU 指令的处理都会在 process() 这个方法中被执行。一般我们执行以下五个过程来处理 APDU 指令:

  1. 获得 APDU 指令的缓冲 (buffer)
    我们用 getBuffer() 方法来得到 APDU 指令的缓冲,在缓冲中有指令信息(CLA,INS,P1,P2,Lc)和数据(Data Field)。
  2. 获取 APDU 指令中的数据
    如果 APDU 指令中包含数据(Data Field)信息,那么我们必须用 setIncomingAndReceive() 方法来获取 Data Field 中的信息。因为在使用 setIncomingAndReceive() 方法之前,APDU 缓冲中只有指令信息有效,即只有 CLA,INS,P1,P2 和 Lc 这前五个字节有效。只有使用了 setIncomingAndReceive() 方法后,数据信息(Data Field)才会被加到缓冲的中,并在这五个字节之后。使用这种方式,主要是因为数据信息(Data Field)并不是必须的,有些 APDU 指令是没有数据信息的,所以在处理 APDU 指令时,一般先得到指令(INS),根据指令判断这一指令是否有数据信息,若有,则使用 setIncomingAndReceive() 方法,使数据信息先加到缓冲中,并可从缓冲的第六个字节起来获取数据信息;若无,则不调用 setIncomingAndReceive() 方法。
  3. 分析处理 APDU 数据
    分析处理 APDU 数据是指 Applet 收到数据后,对数据进行分析,并按照一定的流程来处理数据的过程。例如,本例子中,当 Applet 收到存款指令和存款金额后,会对存款金额进行值域的分析,若值域符合要求,则将当前的存款金额加到存储总金额上。
  4. 返回数据
    返回数据过程是指:Applet 处理完数据后,向终端返回状态字与数据的过程。一般它的处理过程是这样的:先用 setOutgoing() 方法来设置数据传送的方向,表明是 Applet 输出数据,同时 setOutgoing() 方法会返回参数 Le。Le 是指智能卡能够回传数据的长度。然后,我们用 setOutgoingLength() 方法告诉终端:Applet 实际回传数据的长度。而后,我们将要回传的数据设置到 APDU 的缓冲(buffer)中,用 sendBytes() 方法来传送数据。请参阅下面例子中的 getBalance 函数。
  5. e)返回状态字
    当 process() 方法正常执行后,Applet 会给终端自动发出 0x9000 返回状态字,表明 APDU 指令操作成功。如果在 process() 方法执行过程中,Applet 发现了一些错误,我们可以用 ISOException.throwIt(short reason) 方法来向终端发出一些特定的状态字,来告知终端什么样的错误发生了。

以下就是这个“电子钱包”的例子代码:

package trans;
    //输入 javacardx.framework.*;
    import javacard.framework.*;
    public class trans extends Applet {
      //APDU的指令
      final static byte VERIFY = (byte) 0x20;//验证PIN码命令
      final static byte CREDIT = (byte) 0x30;//存款命令
      final static byte DEBIT = (byte) 0x40;//取款命令
      final static byte GETBALANCE = (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;
      //一些返回值
      //验证PIN 码失败
      final static short SW_VERIFICATION_FAILED =0x6300;
      //此操作需验证PIN码
      final static short SW_PIN_VERIFICATION_REQUIRED =0x6301;
      //存取金额值无效
      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;
      //存款余额,最大值为0X7FFF = 32767
      short balance;
      private trans (byte[] bArray,short bOffset,byte bLength){
        //创建OwnerPIN,PIN 的最多尝试数为PIN_TRY_LIMIT,
        //长度最多为MAX_PIN_SIZE
        pin = new OwnerPIN(PIN_TRY_LIMIT,   MAX_PIN_SIZE);
        //安装Applet 时,会传送PIN 的参数
        pin.update(bArray, bOffset, bLength);
        //注册
        register();
      }
      public static void install(byte[] bArray, short bOffset, byte bLength){
        //创建trans 实例,调用构造函数
        new trans(bArray, bOffset, bLength);
      }
      public boolean select() {
        //如果PIN 码锁死,Applet 将不能被选择
        if ( pin.getTriesRemaining() == 0 )
           return false;
        return true;
      }
      public void deselect() {
        //重制PIN
        pin.reset();
      }
      public void process(APDU apdu) {
        //用一个字节数组来处理APDU的头信息,和数据信息
        // buffer 就是这个字节数组
        byte[] buffer = apdu.getBuffer();
        //cla为指令集
        byte cla = buffer[ISO7816.OFFSET_CLA];
        //ins为指令
        byte ins = buffer[ISO7816.OFFSET_INS];
        //检验指令集(cla)是否是正确,我们的Applet 指令
        //集为”B0“,如果指令集错,我们将返回”指令
        //未知“的错误响应
        if (cla != 0xB0)
           ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        //根据指令(ins),完成相应的命令
        switch (ins) {
          case GETBALANCE:    getBalance(apdu);//余额查询
                              return;
          case DEBIT:         debit(apdu);//取款
                              return;
          case CREDIT:        credit(apdu);//存款
                              return;
          case VERIFY:        verify(apdu);//验证PIN码
                              return;
          default:       ISOException.throwIt
                        (ISO7816.SW_INS_NOT_SUPPORTED);//指令未知
        }
     }
     private void credit(APDU apdu) {
       //检验PIN 码是否已被检验,若
       //否,则无权存款,并返回相应错误代码
       if ( ! pin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       //用一个字节数组来处理APDU的头信息,和数据信息
        byte[] buffer = apdu.getBuffer();
        //OFFSET_LC 用来得到APDU中数据信息的长度
        byte numBytes = buffer[ISO7816.OFFSET_LC];
        //APDU中实际得到的信息的长度
        byte byteRead =
                  (byte)(apdu.setIncomingAndReceive());
        //如果长度不匹配,返回相应错误信息
        if ( ( numBytes != 1 ) || (byteRead != 1) )
         ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        //从缓冲的第六个字节得到存储金额
        //ISO7816.OFFSET_CDATA=5
        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);
      }
      private void debit(APDU apdu) {
        //检验PIN 码是否已被检验,若
        //否,则无权取款,并返回相应错误代码
        if ( ! pin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
        //用一个字节数组来处理APDU的头信息,和数据信息
        byte[] buffer = apdu.getBuffer();
        //OFFSET_LC 用来得到APDU中数据信息的长度
        byte numBytes =
                (byte)(buffer[ISO7816.OFFSET_LC]);
        //APDU中实际得到的信息的长度
        byte byteRead =
                (byte)(apdu.setIncomingAndReceive());
        //如果长度不匹配,返回相应错误信息
        if ( ( numBytes != 1 ) || (byteRead != 1) )
           ISOException.throwIt
                            (ISO7816.SW_WRONG_LENGTH);
        //从缓冲的第六个字节获取取款数据
        //ISO7816.OFFSET_CDATA=5
        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);
      }
      private void getBalance(APDU apdu) {
        //用一个字节数组来处理APDU的头信息,和数据信息
        byte[] buffer = apdu.getBuffer();
        //setOutgoing()方法是告知终端Applet准备回传相应
        //并得到期望响应长度
        short le = apdu.setOutgoing();
        //响应长度小于2,出错
        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缓冲区中从0 位置后2 个字节长度的信息发出
        apdu.sendBytes((short)0, (short)2);
      }
      private void verify(APDU apdu) {
      //用一个字节数组来处理APDU的头信息,和数据信息
      byte[] buffer = apdu.getBuffer();
      //APDU中实际得到的信息的长度,即PIN码长度
      byte byteRead =
                (byte)(apdu.setIncomingAndReceive());
      //验证PIN码,PIN码值在apdu缓冲中,从
      //ISO7816.OFFSET_CDATA=5位置起,
      //长度=byteRead,
      if ( pin.check(buffer, ISO7816.OFFSET_CDATA,
           byteRead) == false )
         ISOException.throwIt(SW_VERIFICATION_FAILED);
    }
    }

当 JAVA 卡 Applet 开发完毕后,Cap 文件就可以被装载到 JAVA 智能卡上。安装时,我们需要使用读卡器。一般读卡器与 PC 之间的通信使用传统的串口,并口,或 USB 接口。PC 通过串口或 USB 接口向读卡器发送一定标准的指令,如微软的 PS/SC 接口指令,或读卡器制造商提供的接口指令。从而能让读卡器向智能卡发送装载(install)的 APDU 指令,完成 Applet 的装载。有关对读卡器的操作,超出了我们本次讨论的范围,如有兴趣,可与我联系。

通过本系列的三篇文章,我们介绍了智能卡,JAVA 卡和 JAVA 卡 Applet 的一些基本知识,希望读者通过阅读这一系列,能对 JAVA 卡和基于 JAVA 卡的一些 Applet 有所了解。如果你对 JAVA 卡和 JAVA 卡 Applet 有疑问,或对本系列有疑问,请 email 告诉我(sjbao@iname.com)。

阅读(2913) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~