Java 智能卡迷你计算器






4.31/5 (6投票s)
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
以了解计算过程中发生了什么。如果卡返回0x9000
的STATUS 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个有符号值。