本教程旨在帮助Java智能卡技术的初学者理解基本概念和智能卡与主机应用程序之间的通信。智能卡技术初学者通常会提出一些基础问题,因此决定提供一个完整的示例来帮助他们入门。
在本文中,将解释一个示例应用程序——一个执行四则运算(加、减、乘、除)的计算器。
要理解本教程,需要了解J2SE,并且最好对Java智能卡有基本的了解。要了解Java卡是什么,请访问Oracle的官方网站。此外,可能需要对以下标准有基本的了解:
假设拥有智能卡和智能卡读卡器,并且能够加载和安装本教程/文章中提供的.cap文件。
Netbeans, Java智能卡, Dell键盘读卡器。
智能卡应用程序是什么?
智能卡上的应用程序称为智能卡应用程序。它在计算机上编写,然后安装到智能卡上。
主机应用程序是什么?
主机应用程序是位于计算机上并与智能卡通过APDUs进行交互的应用程序。这个应用程序可以用任何编程语言编写。
什么是APDU?
APDU代表应用程序编程数据单元。它是应用程序和主机应用程序之间的通信媒介。所有的通信都是通过APDUs在主机应用程序和应用程序之间进行的。APDU有两种类型,一种是命令APDU,由主机应用程序发送到应用程序;另一种是响应APDU,由应用程序作为命令APDU的响应发送回主机应用程序。
APDU结构是什么?
APDU由以下字段组成:
Java卡应用程序是一种客户端-服务器应用程序,智能卡始终保持空闲状态,并响应主机应用程序发送的命令。在任何智能卡应用程序中,需要检测与计算机连接的读卡器,然后与该读卡器建立连接,并与读卡器内的智能卡建立连接。
在计算器应用程序中,使用一个组合框来显示所有可用的读卡器,并且有一个名为“刷新”的按钮,当点击该按钮时,组合框会用连接的终端/读卡器填充。之后,需要选择一个终端并点击“连接”按钮以与智能卡建立连接。
使用的是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 getTerminals() throws Exception {
factory = TerminalFactory.getDefault();
terminals = factory.terminals().list();
return terminals;
}
上述函数返回可以在组合框中显示的读卡器列表。
从组合框中选择一个终端后,用户需要点击“连接”按钮。如果卡存在并且其ATR正常工作,则在框架的右下角显示“已连接”文本,否则显示相应的错误消息。
为了与智能卡建立连接,使用CardTerminal类的Connect()函数。要通过T=0建立连接,需要使用Connect("T=0"),对于T=1,需要使用Connect("T=1")。但是,如果不确定,可以使用*,SmartCardIO将自动检测通信协议。
protected void connectToCard(CardTerminal terminalSource) throws CardException {
terminal = terminalSource;
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);
}
}
在上述函数中,首先选择应用程序,然后在成功选择后,准备APDU,该APDU将指示应用程序需要做什么以及它拥有什么数据。
command = "00A404000E63616C63756C61746F722E61707000";
上述是选择应用程序APDU,因为首先需要选择计算器应用程序,以便进行计算,否则默认应用程序可能不会接受后续命令APDUs。
byte[] apdu = JavaSmartcard.hexStringToByteArray(command);
在准备APDU后,将其转换为字节数组以传输到卡片。hexStringToByteArray是一个实用函数,用于将十六进制字符串转换为字节数组。
command = "A000000002";
上述APDU A000000002将告诉应用程序需要添加给定的两个数字。将在这里解释它的字段:
数据部分的计算如下:
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;
}
将两个文本字段的输入转换为字节,然后将这些字节复制到字节数组中以传输到卡片。
最终的APDU如下所示。假设用户输入了5和5:
A0 00 00 00 02 05 05
当智能卡收到该APDU时,它将解释它并找到INS字段以了解主机应用程序想要做什么,然后它将从数据部分获取数据(数字)并将其相加,以响应APDU的形式返回给主机应用程序。
当收到响应APDU时,可以通过STATUS WORD了解计算过程中发生了什么。如果卡片返回STATUS WORD为0x9000,则表示一切正常,否则可能存在错误或需要执行进一步的操作以获取实际的响应APDU。
public int getStatusWords() {
return rAPDU.getSW();
}
上述函数用于获取卡片返回的STATUS WORD,使用以下函数获取数据部分。
public byte[] getData() {
if (rAPDU != null) {
return rAPDU.getData();
} else {
return null;
}
}
其余的代码简单且不言自明。将尝试添加另一个教程,介绍如何编写计算器应用程序,将在本文中附上。