1. 程式人生 > >使用Java驅動ACR122U對IC卡進行讀寫,總結

使用Java驅動ACR122U對IC卡進行讀寫,總結

1。站在他的肩膀上,快速的看完,動手自己實戰了下。對過程寫下總結。總歷時3.5小時。

2。手上有一個ACR122U,讀卡器。不貴有條件的買一個,畢竟是神器,很好用。

3。那文中提示的JavaCard文件,和,ACR官方的文件。很重要,是核心內容。

就像數學中的公式概念,定義。沒有它,就沒有假設和規範。所以有必要重申下,連結。

https://docs.oracle.com/javase/7/docs/jre/api/security/smartcardio/spec/javax/smartcardio/package-summary.html

https://www.acs.com.hk/download-manual/933/API-ACR122U-CN-2.04.pdf

4。首先按rtz的原文,動手。我附上原始碼,且做了比較的註解。

原始碼中,的思路要說下的。即大綱,首先是TerminalFactory找裝置讀卡器,放到一個List裡,一般接一個。

CardTerminal在讀卡器列表中找第一個。並將讀卡器,處於工作連線狀態,即等你放卡狀態。一放卡,就讀卡號,打印出來。

接著做,載入認證金鑰,即將金鑰臨時存放在讀卡器上,至於為什麼,你想讀一個扇區,它有總共4塊,每一塊都要認證,不可能每次都載入金鑰,存放在讀卡器上,就方便多了,甚至別的扇區和塊,如果密碼一樣,也可以直接用。接下來,認證,就是從將拿存放在讀卡器上的金鑰對某個塊進行認證,通過就,可以讀寫那個塊的資料了。

5。有機會,可以用java的圖形開發,實現。

package com.ddzh.acr;

import java.util.List;

import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;

/**
 * 描 述: <描述>.acr.java
 * 
 * @author guolp
 * @since 1.0, 2018-10-24 09:20:16
 */
public class acr {

	/**
	 * <一句話描述該方法的功能>
	 * 
	 * @param args
	 * @since 1.0, 2018-10-24 09:20:19
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TerminalFactory factory = TerminalFactory.getDefault();// 得到一個預設的讀卡器工廠(迷。。)
		List<CardTerminal> terminals;// 建立一個List用來放讀卡器(誰沒事會在電腦上插三四個讀卡器。。)
		try {
			terminals = factory.terminals().list();// 從工廠獲得插在電腦上的讀卡器列表,get讀卡器列表
			terminals.stream().forEach(s -> System.out.println(s));// 列印獲取到的讀卡器名稱

			CardTerminal a = terminals.get(0);// 使用第0個讀卡器[暫且不考慮同時插N個讀卡器的情況了]
			a.waitForCardPresent(0L);// 等待放置卡片
			Card card = a.connect("T=1");// 連線卡片,協議T=1 塊讀寫(T=0貌似不支援,一用就報錯)
			CardChannel channel = card.getBasicChannel();// 開啟通道
			CommandAPDU getUID = new CommandAPDU(0xFF, 0xCA, 0x00, 0x00, 0x04);// 中文API第12頁
			ResponseAPDU r = channel.transmit(getUID);// 傳送getUID指令
			System.out.println("UID: " + r.toString());// 返回:UID: ResponseAPDU: 6 bytes, SW=9000
			System.out.println("Data:" + bytesToHexString(r.getData()));
			// 即返回卡號,操作成功

			// 載入認證金鑰,放在讀卡器的EEPROM
			byte[] pwd = { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };// 先用一個數組把金鑰存起來
			CommandAPDU loadPWD = new CommandAPDU(0xFF, 0x82, 0x00, 0x00, pwd, 0, 6);// 構造載入認證金鑰APDU指令 ,中文API第13頁
			ResponseAPDU r1 = channel.transmit(loadPWD);// 傳送loadPWD指令
			System.out.println("載入認證金鑰: " + r1.toString());

			// 認證金鑰,驗證通過後,讀取同一扇區的其他塊不需要再次驗證,中文API原文
			byte[] check = { (byte) 0x01, (byte) 0x00, (byte) 0x0B, (byte) 0x60, (byte) 0x00 };
			// 0x01認證版本,0x00空閒,0x08認證區塊號,0x60金鑰型別A/0x61金鑰型別B,0x00金鑰儲存的地址(金鑰號)
			CommandAPDU authPWD = new CommandAPDU(0xFF, 0x86, 0x00, 0x00, check, 0, 5);// 加上指令頭部,構造出完整的認證APDU指令,中文API第14頁
			ResponseAPDU r2 = channel.transmit(authPWD);// 傳送 認證指令
			System.out.println("認證第8區塊:" + r2.toString());

			// 讀區塊2
			CommandAPDU getData8 = new CommandAPDU(0xFF, 0xB0, 0x00, 0x08, 0x10);// 構造 讀區塊APDU指令,中文API第17頁
			ResponseAPDU r8 = channel.transmit(getData8);// 傳送 讀區塊指令
			System.out.println("第8個區塊,Data:" + bytesToHexString(r8.getData()));

			CommandAPDU getData9 = new CommandAPDU(0xFF, 0xB0, 0x00, 0x09, 0x10);// 構造 讀區塊APDU指令
			ResponseAPDU r9 = channel.transmit(getData9);// 傳送 讀區塊指令
			System.out.println("第9個區塊,Data:" + bytesToHexString(r9.getData()));

			CommandAPDU getData10 = new CommandAPDU(0xFF, 0xB0, 0x00, 0x0A, 0x10);// 構造 讀區塊APDU指令
			ResponseAPDU r10 = channel.transmit(getData10);// 傳送 讀區塊指令
			System.out.println("第10個區塊,Data:" + bytesToHexString(r10.getData()));

			CommandAPDU getData11 = new CommandAPDU(0xFF, 0xB0, 0x00, 0x0B, 0x10);// 構造 讀區塊APDU指令
			ResponseAPDU r11 = channel.transmit(getData11);// 傳送 讀區塊指令
			System.out.println("第11個區塊,Data:" + bytesToHexString(r11.getData()));

			// 寫區塊
			byte[] up = { (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06,
					(byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0A, (byte) 0x0B, (byte) 0x0C, (byte) 0x0D,
					(byte) 0x0E, (byte) 0x0F };// 0x 構造要寫入的資料,有16位
			CommandAPDU upData = new CommandAPDU(0xFF, 0xD6, 0x00, 0x08, up, 0, 16);//構造 寫區塊APDU指令,中文API第18頁
			ResponseAPDU r4 = channel.transmit(upData);// 傳送寫塊指令
			System.out.println("寫區塊: " + r4.toString());
			System.out.println("寫區塊: " + bytesToHexString(r4.getData()) + ":#:" + bytesToHexString(r4.getBytes()));// 列印返回值
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * <一句話描述該方法的功能>
	 * 
	 * @param data
	 * @return
	 * @author guolp
	 * @since 1.0, 2018-10-24 09:28:48
	 */
	private static final char[] HEX_CHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
			'f' };

	private static String bytesToHexString(byte[] bytes) {
		// TODO Auto-generated method stub
		StringBuilder sb = new StringBuilder();
		int a = 0;
		for (byte b : bytes) { // 使用除與取餘進行轉換
			if (b < 0) {
				a = 256 + b;
			} else {
				a = b;
			}
			// sb.append("0x");
			sb.append(HEX_CHAR[a / 16]);
			sb.append(HEX_CHAR[a % 16]);
			// sb.append(" ");
		}
		return sb.toString().toUpperCase();
	}

}