使用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();
	}

}

 

posted @ 2018-10-24 11:28  glpa  阅读(407)  评论(0编辑  收藏  举报