標籤:des android style blog http io os ar 使用
本文來自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的功能。
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。
核心源碼如下:
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 registrationnew 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);}}
接下來貼出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"。
核心源碼如下:
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 intentTECHLISTS = 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 deviceisoDep.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; }}
移動支付之智能IC卡與Android手機進行NFC通訊