65.9K
CodeProject 正在变化。 阅读更多。
Home

Java 智能卡迷你计算器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.31/5 (6投票s)

2013 年 2 月 15 日

CPOL

6分钟阅读

viewsIcon

43354

downloadIcon

2830

Java 卡和主机应用程序之间的通信演示

引言

本文介绍如何编写基于Java智能卡的应用程序。本教程将帮助初学者理解Java智能卡与主机应用程序之间的概念和通信。我注意到Java智能卡技术的初学者经常提出简单的问题,所以我决定为他们提供一个完整的示例,帮助他们入门。 

在本文/教程中,我将解释一个示例应用程序,一个计算器,它将执行四种基本的计算运算,即 +、-、* 和 /。

背景

为了理解本教程,您必须了解J2SE并对(Java)智能卡有基本了解。要了解Java Card是什么,请访问Oracle官方网站这里

此外,您可能还需要了解以下标准的.*.

  • ISO 7816-3
  • ISO 7816-4
  • Global Platform 2.1/2.2  

假设

我假设您已经拥有智能卡和智能卡读卡器,并且能够加载和安装本教程/文章附带的*.*cap*文件。

使用的工具

  • Netbeans,
  • Java智能卡
  • 戴尔键盘读卡器

定义  

什么是智能卡Applet? 

驻留在智能卡上的应用程序称为智能卡Applet。它在计算机上编写,然后安装到智能卡上。 

什么是主机应用程序?

它是驻留在计算机上或通过APDUs与智能卡交互的应用程序。此应用程序可以用任何编程语言编写。

什么是APDU?

APDU是Application Programming Data Unit的首字母缩写。它是Applet和主机应用程序之间的通信媒介。所有通信都通过APDUs在主机应用程序和Applet之间进行。 

APDU有两种类型:一种是命令APDU,由主机应用程序发送到Applet;另一种是响应APDU,由.*.*作为命令APDU的响应发送回主机应用程序。

APDU的结构是什么?

APDU包含以下字段: 

  • CLA:APDU的类 
  • INS:主机应用程序想要执行的指令 
  • P1 & P2:切换参数  
  • LC:数据的长度
  • Data:正在发送到卡的实际数据
  • LE:从卡预期的数据长度

以上字段的顺序应该是

CLA INS P1 P2 LC  Data  LE

使用代码

Java Card应用程序是一种客户端-服务器应用程序,其中智能卡始终处于空闲状态,并响应发送到它的命令。总是有一个命令APDU的响应APDU。

在任何智能卡应用程序中,我们需要检测连接到计算机的读卡器,然后与该读卡器建立连接,并连接到该读卡器中的卡。

在计算器应用程序中,我使用一个组合框来显示所有可用的读卡器,以及一个名为“刷新”的按钮,点击它会用连接的终端/读卡器填充组合框。之后,您需要选择一个终端并单击“连接”按钮以建立与智能卡的连接。 

我正在使用SmartCardIO API,它是JDK 1.6+自带的,这意味着您无需下载它,只需导入即可使用。在计算器应用程序中,使用了SmartCardIO的以下类:

 

import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;

要开始与卡通信,我们需要首先获取读卡器/终端。为此,Java提供了一个名为TerminalFactory的类,该类用于获取连接到计算机的所有终端。

public List<CardTerminal> getTerminals() throws Exception{
    factory = TerminalFactory.getDefault();
    terminals = factory.terminals().list();

    return terminals;
}

上面的函数返回一个我们可以显示在组合框中的读卡器列表List

从组合框中选择一个终端后,用户必须单击“连接”按钮。如果存在卡并且其ATR工作正常,则会在框架的右下角显示文本“已连接”,否则会显示相应的错误消息。

 

为了连接到智能卡,我们使用的是CardTerminal类的Connect()函数。要通过T=0连接,您需要使用Connect ("T=0"),对于T=1,您必须使用Connect("T=1")。但是,如果您不确定,可以使用*SmartCardIO将自动检测通信协议。

以下方法执行连接操作:

protected void connectToCard(CardTerminal terninalSource) throws CardException {
    terminal = terninalSource;
    card = terminal.connect("*");
}

成功连接到卡后,您需要在输入文件中输入数字,然后按计算操作按钮。 

我将在此解释(+)操作,其余的都一样。

private void add_buttonActionPerformed(java.awt.event.ActionEvent evt) {                                           
        
    String command = "00A404000E63616C63756C61746F722E61707000";
    byte[] apdu = JavaSmartcard.hexStringToByteArray(command);
    if (!selectApplet(apdu))
    {
        return;
    }
    
    byte[] data_LC;
    try 
    {
        data_LC = getLCData(this.digit1_TextField.getText(), this.digit2_TextField.getText());
    }
    catch (Exception ex) 
    {
       JOptionPane.showMessageDialog(this, "Only digits are allowed to input in the fields\n"+
         ex.getMessage(), "Type Error", JOptionPane.ERROR_MESSAGE);
       return;
    }
    
    command = "A000000002"; 
    
    String LC_Hex = JavaSmartcard.byteArrayToHexString(data_LC);     
         
    command = command.concat(LC_Hex);                
    
    apdu = JavaSmartcard.hexStringToByteArray(command);
    System.out.println(""+ JavaSmartcard.htos(apdu));
    try
    {
        javaCard.sendApdu(apdu);
        byte[] data = javaCard.getData();
        
        this.status_Label.setText(""+Integer.toHexString(javaCard.getStatusWords()).toUpperCase());
        this.result_Label.setText(new BigInteger(data)+"");
    } 
    catch (CardException | IllegalArgumentException ex) 
    {
        JOptionPane.showMessageDialog(this, "Error while tried to send command APDU\n"+
          ex.getMessage()+"", "APDU sending fail", JOptionPane.ERROR_MESSAGE);
    }
}

在上面的函数中,我首先选择Applet,并在成功选择后准备APDU,该APDU将指示Applet做什么以及它拥有的数据。 

command = "00A404000E63616C63756C61746F722E61707000"; 

上面是选择Applet的APDU,因为我们需要先选择我们的计算器Applet才能进行计算,否则默认Applet可能不会接受您后续的命令APDU。

byte[] apdu = JavaSmartcard.hexStringToByteArray(command); 

准备好APDU后,我将将其转换为字节数组以传输到卡。hexStringToByteArray是一个用于将十六进制字符串转换为字节数组的实用函数。

command = "A000000002"; 

上面的APDUA000000002是告诉Applet您需要添加给定两个数字的APDU。我将在下面解释其字段:

  • CLA:AO
  • INS: 00
  • P1 & P2  00, 00
  • LC 02 

数据部分计算如下:

private byte[] getLCData(String byte1Str, String byte2Str) throws Exception
{
    byte[] data_LC = new byte[2];
    byte byte1 =  Byte.parseByte(byte1Str );
    byte byte2 =  Byte.parseByte(byte2Str);
    data_LC[0] = byte1;
    data_LC[1] = byte2;
  
    return data_LC;
}

我将两个文本字段的输入转换为字节,然后将这些字节复制到一个字节数组中以传输到卡。

如果用户输入5和5并进行输入,最终的APDU将如下所示。

A0 00 00 00 02 05 05

当智能卡收到该APDU时,它将对其进行解释,并查找INS字段以了解主机应用程序想要做什么,然后它将从数据部分获取数据(数字),并将其作为响应APDU返回给主机应用程序。

收到响应APDU后,我们可以确定STATUS WORD以了解计算过程中发生了什么。如果卡返回0x9000STATUS WORD,则表示一切正常,否则可能存在错误,或者需要执行进一步的操作来获取实际的响应APDU。

public int getStatusWords() {
    return rAPDU.getSW();
}

上面的函数用于获取卡返回的STATUS WORD,并使用下面的函数获取数据部分。

public byte[] getData() {
    if (rAPDU!=null) {
        return rAPDU.getData();
    }
    else {
        return null;
    }
}

其余代码简单易懂。我将尝试添加另一篇关于编写我附在此文章中的计算器Applet的教程。 

 

更多阅读请访问博客: https://gosiebel.blogspot.com/

兴趣点 

在此示例中,没有使用任何特殊的第三方DLL、库或API。所有操作都通过Java提供的SDK完成。

限制

由于智能卡是资源有限且计算能力有限的设备,因此此计算器的工作范围是1-127(包含)。原因是我们将输入转换为字节,而一个字节可以包含127个有符号值。

© . All rights reserved.