NFC 开发

本文来自http://blog.csdn.net/hellogv/ ,引用必须注明出处!
 
       目前常见的智能IC卡运行着JavaCard虚拟机,智能IC卡上可以运行由精简后的Java语言编写的卡应用(简称Applet)。智能IC卡的Applet不能自己启动,必须由外部终端(例如POS机,地铁刷卡终端等)向卡片发送Select命令,由此选中卡片的Applet,Applet才能运行。Appplet侧重于数据的处理,没有花销的I/O功能。Applet的程序有生命周期和指定入口,其中最主要的几个方法如下:
  • 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。

核心源码如下:

 

  1. <span style="font-family:Microsoft YaHei;">public class mytest extends Applet {  
  2.   
  3.     private static final byte[] strHello= { (byte) 'H', (byte) 'e',  
  4.             (byte) 'l', (byte) 'l', (byte) 'o'};  
  5.   
  6.     private static final byte[] strWorld = {(byte) 'W',  
  7.         (byte) 'o', (byte) 'r', (byte) 'l', (byte) 'd', };  
  8.   
  9.     private static final byte CMD_CLA = (byte) 0x80;  
  10.     private static final byte CMD_INS_1 = (byte) 0x10;  
  11.     private static final byte CMD_INS_2 = (byte) 0x20;  
  12.       
  13.     public static void install(byte[] bArray, short bOffset, byte bLength) {  
  14.         // GP-compliant JavaCard applet registration  
  15.         new mytest().register(bArray, (short) (bOffset + 1), bArray[bOffset]);  
  16.     }  
  17.   
  18.     /* 
  19.      * 当Java卡Applet被选中时,由JCRE调用。Java卡Applet可以定义select()完成初始化, 
  20.      * 否则,JCRE调用父类的select()。 
  21.      * @see javacard.framework.Applet#select() 
  22.      */  
  23.     public boolean select() {  
  24.         short debug=100;  
  25.         debug++;//用于断点调试,当被select时触发。  
  26.         return super.select();  
  27.     }  
  28.   
  29.     /* 
  30.      * 当Java卡Applet被放弃时,由JCRE调用。Java卡Applet可以定义deselect()完成清除, 
  31.      * 否则,JCRE调用父类的deselect()。 
  32.      * @see javacard.framework.Applet#deselect() 
  33.      */  
  34.     public void deselect() {  
  35.         short debug=100;  
  36.         debug++;//用于断点调试  
  37.         super.deselect();  
  38.     }  
  39.    
  40.     /* 
  41.      * 每次收到APDU命令,都会执行 
  42.      * @see javacard.framework.Applet#process(javacard.framework.APDU) 
  43.      */  
  44.     public void process(APDU apdu) {  
  45.         if (selectingApplet()) {  
  46.             return;  
  47.         }  
  48.   
  49.         //获取外部终端发过来的数据  
  50.         byte[] buffer = apdu.getBuffer();  
  51.         //获取第一位数据  
  52.         byte CLA = (byte) (buffer[ISO7816.OFFSET_CLA] & 0xFF);  
  53.         //获取第二位数据  
  54.         byte INS = (byte) (buffer[ISO7816.OFFSET_INS] & 0xFF);  
  55.   
  56.         if (CLA != CMD_CLA) {//格式不对  
  57.             ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);  
  58.         }  
  59.   
  60.         switch (INS) {  
  61.         case CMD_INS_1:  
  62.             sendBytes(apdu,strHello);  
  63.             break;  
  64.         case CMD_INS_2:  
  65.             sendBytes(apdu,strWorld);  
  66.             break;  
  67.         default:  
  68.             ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);  
  69.         }  
  70.     }  
  71.   
  72.     private void sendBytes(APDU apdu,byte[] arrays) {  
  73.         byte[] buffer = apdu.getBuffer();  
  74.         short length = (short) arrays.length;  
  75.   
  76.         Util.arrayCopyNonAtomic(arrays, (short) 0, buffer, (short) 0,  
  77.                 (short) length);  
  78.   
  79.         apdu.setOutgoingAndSend((short) 0, length);  
  80.     }  
  81. }</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"。

核心源码如下:

  1. <span style="font-family:Microsoft YaHei;">public final class CardReader {  
  2.     private static final String TAG = "LoyaltyCardReader";  
  3.     // AID for our loyalty card service.  
  4.     private static final String SAMPLE_CARD_AID = "1122001122";  
  5.     // ISO-DEP command HEADER for selecting an AID.  
  6.     // Format: [Class | Instruction | Parameter 1 | Parameter 2]  
  7.     private static final String SELECT_APDU_HEADER = "00A40400";  
  8.     // "OK" status word sent in response to SELECT AID command (0x9000)  
  9.     private static final byte[] SELECT_OK_SW = {(byte) 0x90, (byte) 0x00};  
  10.     //自定义的命令  
  11.     private static final String[] SAMPLE_COMMAND={"8010000000",//卡片收到后返回"Hello"  
  12.         "8020000000"};//卡片收到后返回"World"  
  13.       
  14.     public static String[][] TECHLISTS;  
  15.     public static IntentFilter[] FILTERS;  
  16.   
  17.     static {  
  18.         try {  
  19.             //the tech lists used to perform matching for dispatching of the ACTION_TECH_DISCOVERED intent  
  20.             TECHLISTS = new String[][] { { IsoDep.class.getName() }};  
  21.   
  22.             FILTERS = new IntentFilter[] { new IntentFilter(  
  23.                     NfcAdapter.ACTION_TECH_DISCOVERED, "*/*") };  
  24.         } catch (Exception e) {  
  25.         }  
  26.     }  
  27.       
  28.     static public String tagDiscovered(Tag tag) {  
  29.         Log.e(TAG, "New tag discovered");  
  30.   
  31.         String strResult="";  
  32.         IsoDep isoDep = IsoDep.get(tag);  
  33.         if (isoDep != null) {  
  34.             try {  
  35.                 // Connect to the remote NFC device  
  36.                 isoDep.connect();  
  37.   
  38.                 //发送select 命令,卡片会返回SELECT_OK_SW(90 00)  
  39.                 byte[] cmdSelect = BuildSelectApdu(SAMPLE_CARD_AID);  
  40.                 Log.e(TAG, "Sending: " + ByteArrayToHexString(cmdSelect));  
  41.                 byte[] result = isoDep.transceive(cmdSelect);  
  42.                 Log.e(TAG, "Receive: " + ByteArrayToHexString(result));  
  43.                 byte[][] response = getResponse(result);  
  44.                 byte[] statusWord =response[0];  
  45.   
  46.                 if (Arrays.equals(SELECT_OK_SW, statusWord) == false)  
  47.                     return "";  
  48.   
  49.                 //循环发送自定义命令  
  50.                 for(int i=0;i<SAMPLE_COMMAND.length;i++){  
  51.                     String command = SAMPLE_COMMAND[i];  
  52.                     result = HexStringToByteArray(command);  
  53.                     Log.e(TAG, "Sending: " + command);  
  54.                       
  55.                     result = isoDep.transceive(result);  
  56.                     Log.e(TAG, "Receive: " + ByteArrayToHexString(result));  
  57.                     response = getResponse(result);  
  58.                     byte[] body =response[1];  
  59.                       
  60.                     strResult=strResult+new String(body)+":"+ByteArrayToHexString(body)+"\r\n";  
  61.                 }  
  62.                   
  63.   
  64.                 return strResult;  
  65.   
  66.             } catch (IOException e) {  
  67.                 Log.e(TAG, "Error communicating with card: " + e.toString());  
  68.             }  
  69.         }  
  70.         return null;  
  71.     }  
  72.   
  73.     /***  
  74.      * 分解卡片返回的数据  
  75.      * @param b  
  76.      * @return [0]表示返回的状态值,[1]表示返回的正文  
  77.      */  
  78.     private static byte[][] getResponse(byte[] b){  
  79.         byte[][] result = new byte[2][];  
  80.           
  81.         int length = b.length;  
  82.         byte[] status = { b[length - 2],b[length - 1] };  
  83.           
  84.         byte[] body = Arrays.copyOf(b, length - 2);  
  85.   
  86.         result[0]=status;  
  87.         result[1]=body;  
  88.         return result;  
  89.     }  
  90.       
  91.     public static String load(Parcelable parcelable) {  
  92.         // 从Parcelable筛选出各类NFC标准数据  
  93.         final Tag tag = (Tag) parcelable;  
  94.         return tagDiscovered(tag);  
  95.     }  
  96.   
  97.   
  98.     /** 
  99.      * Build APDU for SELECT AID command. This command indicates which service a reader is 
  100.      * interested in communicating with. See ISO 7816-4. 
  101.      * 
  102.      * @param aid Application ID (AID) to select 
  103.      * @return APDU for SELECT AID command 
  104.      */  
  105.     public static byte[] BuildSelectApdu(String aid) {  
  106.         // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA]  
  107.         return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid);  
  108.     }  
  109.   
  110.     /** 
  111.      * Utility class to convert a byte array to a hexadecimal string. 
  112.      * 
  113.      * @param bytes Bytes to convert 
  114.      * @return String, containing hexadecimal representation. 
  115.      */  
  116.     public static String ByteArrayToHexString(byte[] bytes) {  
  117.         final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};  
  118.         char[] hexChars = new char[bytes.length * 2];  
  119.         int v;  
  120.         for ( int j = 0; j < bytes.length; j++ ) {  
  121.             v = bytes[j] & 0xFF;  
  122.             hexChars[j * 2] = hexArray[v >>> 4];  
  123.             hexChars[j * 2 + 1] = hexArray[v & 0x0F];  
  124.         }  
  125.         return new String(hexChars);  
  126.     }  
  127.   
  128.     /** 
  129.      * Utility class to convert a hexadecimal string to a byte string. 
  130.      * 
  131.      * <p>Behavior with input strings containing non-hexadecimal characters is undefined. 
  132.      * 
  133.      * @param s String containing hexadecimal characters to convert 
  134.      * @return Byte array generated from input 
  135.      */  
  136.     public static byte[] HexStringToByteArray(String s) {  
  137.         int len = s.length();  
  138.         byte[] data = new byte[len / 2];  
  139.         for (int i = 0; i < len; i += 2) {  
  140.             data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)  
  141.                     + Character.digit(s.charAt(i+1), 16));  
  142.         }  
  143.         return data;  
  144.     }  
  145. }</span>  
posted @ 2015-11-30 17:36  Alan_Mathison  阅读(895)  评论(0编辑  收藏  举报