- public static void install(byte[] bArray, short bOffset, byte bLength)
构建了Applet子类的实例,JCRE将会最先调用这个;所有的初始化和分配内存的操作应该在这个里面实现;可以获取卡外实体传进来的一些应用初始化参数。
- public void process(APDU apdu)
类似于正常java class的main,在安装后,APDU的执行将在这里实现。
- protected final void register()
applet用来在JCRE中注册该applet实例
- register(byte[] bArray, short bOffset, byte bLength)
register( )功能一样,增加了可以分配其特定的AID的功能。
- public boolean select()
JCRE一旦接收到SELECT[by name]命令时,将寻找命令中指示的AID对应的Applet,使之处于活跃状态,接收并处理接下来的APDU命令;在选择新的Applet前,JCRE先调用当前Applet的 deselect 方法;Applet可以拒绝被选择,此时 select 方法返回false;SELECT[by name]命令本身也将传递给applet处理,此时通过 selectingApplet 用以判断当前状态。
本文的DEMO运行效果如下,包含一个JavaCard的Applet实现和一个Android端的NFC读写程序,实现智能IC卡与Android手机的简单通信。
接下来贴段简单的Applet 源码,下载地址:http://download.csdn.net/detail/hellogv/8090041。
大概的思路是:Applet定义了2个开头标识皆为CMD_CLA的自定义命令CMD_INS_1和CMD_INS_2,当Android手机通过NFC分别发送CMD_INS_1和CMD_INS_2,Applet分别返回strHello和strWorld。
核心源码如下:
- <span style="font-family:Microsoft YaHei;">public class mytest extends Applet {
- private static final byte[] strHello= { (byte) 'H', (byte) 'e',
- (byte) 'l', (byte) 'l', (byte) 'o'};
- private static final byte[] strWorld = {(byte) 'W',
- (byte) 'o', (byte) 'r', (byte) 'l', (byte) 'd', };
- private static final byte CMD_CLA = (byte) 0x80;
- private static final byte CMD_INS_1 = (byte) 0x10;
- private static final byte CMD_INS_2 = (byte) 0x20;
- public static void install(byte[] bArray, short bOffset, byte bLength) {
- // GP-compliant JavaCard applet registration
- new mytest().register(bArray, (short) (bOffset + 1), bArray[bOffset]);
- }
- /*
- * 当Java卡Applet被选中时,由JCRE调用。Java卡Applet可以定义select()完成初始化,
- * 否则,JCRE调用父类的select()。
- * @see javacard.framework.Applet#select()
- */
- public boolean select() {
- short debug=100;
- debug++;//用于断点调试,当被select时触发。
- return super.select();
- }
- /*
- * 当Java卡Applet被放弃时,由JCRE调用。Java卡Applet可以定义deselect()完成清除,
- * 否则,JCRE调用父类的deselect()。
- * @see javacard.framework.Applet#deselect()
- */
- public void deselect() {
- short debug=100;
- debug++;//用于断点调试
- super.deselect();
- }
- /*
- * 每次收到APDU命令,都会执行
- * @see javacard.framework.Applet#process(javacard.framework.APDU)
- */
- public void process(APDU apdu) {
- if (selectingApplet()) {
- return;
- }
- //获取外部终端发过来的数据
- byte[] buffer = apdu.getBuffer();
- //获取第一位数据
- byte CLA = (byte) (buffer[ISO7816.OFFSET_CLA] & 0xFF);
- //获取第二位数据
- byte INS = (byte) (buffer[ISO7816.OFFSET_INS] & 0xFF);
- if (CLA != CMD_CLA) {//格式不对
- ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
- }
- switch (INS) {
- case CMD_INS_1:
- sendBytes(apdu,strHello);
- break;
- case CMD_INS_2:
- sendBytes(apdu,strWorld);
- break;
- default:
- ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
- }
- }
- private void sendBytes(APDU apdu,byte[] arrays) {
- byte[] buffer = apdu.getBuffer();
- short length = (short) arrays.length;
- Util.arrayCopyNonAtomic(arrays, (short) 0, buffer, (short) 0,
- (short) length);
- apdu.setOutgoingAndSend((short) 0, length);
- }
- }</span>
接下来贴出Android端的核心代码,下载地址:http://download.csdn.net/detail/hellogv/8090053。
大概的思路是:Android端的NFC读写程序定义1个Applet的ID(AID),SELECT命令的报文头(SELECT_APDU_HEADER),2个自定义命令CMD_INS_1和CMD_INS_2。首先使用AID和SELECT_APDU_HEADER生成完整的SELECT命令,transceive(发送)到卡片,用于启动卡片里的AID对应的Applet。启动卡片里的Applet后,NFC读写程序发送SAMPLE_COMMAND里面的2条自定义命令,Applet分别返回"Hello""World"。
核心源码如下:
- <span style="font-family:Microsoft YaHei;">public final class CardReader {
- private static final String TAG = "LoyaltyCardReader";
- // AID for our loyalty card service.
- private static final String SAMPLE_CARD_AID = "1122001122";
- // ISO-DEP command HEADER for selecting an AID.
- // Format: [Class | Instruction | Parameter 1 | Parameter 2]
- private static final String SELECT_APDU_HEADER = "00A40400";
- // "OK" status word sent in response to SELECT AID command (0x9000)
- private static final byte[] SELECT_OK_SW = {(byte) 0x90, (byte) 0x00};
- //自定义的命令
- private static final String[] SAMPLE_COMMAND={"8010000000",//卡片收到后返回"Hello"
- "8020000000"};//卡片收到后返回"World"
- public static String[][] TECHLISTS;
- public static IntentFilter[] FILTERS;
- static {
- try {
- //the tech lists used to perform matching for dispatching of the ACTION_TECH_DISCOVERED intent
- TECHLISTS = new String[][] { { IsoDep.class.getName() }};
- FILTERS = new IntentFilter[] { new IntentFilter(
- NfcAdapter.ACTION_TECH_DISCOVERED, "*/*") };
- } catch (Exception e) {
- }
- }
- static public String tagDiscovered(Tag tag) {
- Log.e(TAG, "New tag discovered");
- String strResult="";
- IsoDep isoDep = IsoDep.get(tag);
- if (isoDep != null) {
- try {
- // Connect to the remote NFC device
- isoDep.connect();
- //发送select 命令,卡片会返回SELECT_OK_SW(90 00)
- byte[] cmdSelect = BuildSelectApdu(SAMPLE_CARD_AID);
- Log.e(TAG, "Sending: " + ByteArrayToHexString(cmdSelect));
- byte[] result = isoDep.transceive(cmdSelect);
- Log.e(TAG, "Receive: " + ByteArrayToHexString(result));
- byte[][] response = getResponse(result);
- byte[] statusWord =response[0];
- if (Arrays.equals(SELECT_OK_SW, statusWord) == false)
- return "";
- //循环发送自定义命令
- for(int i=0;i<SAMPLE_COMMAND.length;i++){
- String command = SAMPLE_COMMAND[i];
- result = HexStringToByteArray(command);
- Log.e(TAG, "Sending: " + command);
- result = isoDep.transceive(result);
- Log.e(TAG, "Receive: " + ByteArrayToHexString(result));
- response = getResponse(result);
- byte[] body =response[1];
- strResult=strResult+new String(body)+":"+ByteArrayToHexString(body)+"\r\n";
- }
- return strResult;
- } catch (IOException e) {
- Log.e(TAG, "Error communicating with card: " + e.toString());
- }
- }
- return null;
- }
- /***
- * 分解卡片返回的数据
- * @param b
- * @return [0]表示返回的状态值,[1]表示返回的正文
- */
- private static byte[][] getResponse(byte[] b){
- byte[][] result = new byte[2][];
- int length = b.length;
- byte[] status = { b[length - 2],b[length - 1] };
- byte[] body = Arrays.copyOf(b, length - 2);
- result[0]=status;
- result[1]=body;
- return result;
- }
- public static String load(Parcelable parcelable) {
- // 从Parcelable筛选出各类NFC标准数据
- final Tag tag = (Tag) parcelable;
- return tagDiscovered(tag);
- }
- /**
- * Build APDU for SELECT AID command. This command indicates which service a reader is
- * interested in communicating with. See ISO 7816-4.
- *
- * @param aid Application ID (AID) to select
- * @return APDU for SELECT AID command
- */
- public static byte[] BuildSelectApdu(String aid) {
- // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA]
- return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid);
- }
- /**
- * Utility class to convert a byte array to a hexadecimal string.
- *
- * @param bytes Bytes to convert
- * @return String, containing hexadecimal representation.
- */
- public static String ByteArrayToHexString(byte[] bytes) {
- final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
- char[] hexChars = new char[bytes.length * 2];
- int v;
- for ( int j = 0; j < bytes.length; j++ ) {
- v = bytes[j] & 0xFF;
- hexChars[j * 2] = hexArray[v >>> 4];
- hexChars[j * 2 + 1] = hexArray[v & 0x0F];
- }
- return new String(hexChars);
- }
- /**
- * Utility class to convert a hexadecimal string to a byte string.
- *
- * <p>Behavior with input strings containing non-hexadecimal characters is undefined.
- *
- * @param s String containing hexadecimal characters to convert
- * @return Byte array generated from input
- */
- public static byte[] HexStringToByteArray(String s) {
- int len = s.length();
- byte[] data = new byte[len / 2];
- for (int i = 0; i < len; i += 2) {
- data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
- + Character.digit(s.charAt(i+1), 16));
- }
- return data;
- }
- }</span>