Android NFC M1卡读写&芯片卡读写(CPU卡读写)(RFID读写)

Android NFC M1卡读写&芯片卡读写(CPU卡读写)(RFID读写)

  • NFC 读写分几种,本文主要讲M1卡扇区读写和芯片卡读写
    • 权限
    • 初始化
      • 1 onCreate( initNFC() )
      • 2 onResume( )
      • 3 onPause()
      • 4 NFC设备刷卡时触发 onNewIntent(Intent)
    • 1,标签读写
    • 2,扇区读写
    • 3 CPU卡读写 重头戏

NFC 读写分几种,本文主要讲M1卡扇区读写和芯片卡读写

NFC 标签读写
NFC 扇区读写
NFC 文件读写

权限

    

    
        
            
                
                 
                
                
            
        

初始化

在Activity#onCreate()注册,在Activity#onResume()开启前台调度系统,在Activity#onPause退出前台调度。

1 onCreate( initNFC() )

private void initNFC() {
		// 获取nfc适配器,判断设备是否支持NFC功能
		nfcAdapter = NfcAdapter.getDefaultAdapter(this);
		if (nfcAdapter == null) {
			shotToast("当前设备不支持NFC功能");
		} else if (!nfcAdapter.isEnabled()) {
			shotToast("NFC功能未打开,请先开启后重试!");
		}
		pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
				getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
		IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
		ndef.addCategory("*/*");
		// 允许扫描的标签类型
		mWriteTagFilters = new IntentFilter[]{ndef};
		mTechLists = new String[][]{
				new String[]{MifareClassic.class.getName()},
				new String[]{NfcA.class.getName()}};// 允许扫描的标签类型
	}

2 onResume( )

	@Override
	protected void onResume() {
		super.onResume();
		//开启前台调度系统
		nfcAdapter.enableForegroundDispatch(this, pendingIntent, mWriteTagFilters, mTechLists);
	}

3 onPause()

	@Override
	protected void onPause() {
		super.onPause();
		nfcAdapter.disableForegroundDispatch(this);
	}

4 NFC设备刷卡时触发 onNewIntent(Intent)

给伪代码,详细见下面3点分解
	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		//当该Activity接收到NFC标签时,运行该方法
		if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()) ||
				NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
			Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
		1,标签读写
			Ndef ndef = Ndef.get(tag);//如果ndef为空表示不支持该格式  
			//可进行格式  如果格式化失败则不能只能换个方式
		2,M1 扇区读写
			MifareClassic mfc = MifareClassic.get(tag);//CPU卡时 mfc将为空
		3,CPU卡 读写
			NfcCpuUtilsnfc = new NfcCpuUtils(IsoDep.get(tag));
			}
	}

1,标签读写

	/**
	 * 写标签
	 * @param ndef
	 * @param tag
	 * @param ndefMessage
	 * @return
	 * @throws IOException
	 * @throws FormatException
	 */
	private boolean writeMsg(Ndef ndef, Tag tag, NdefMessage ndefMessage) throws IOException, FormatException {
		try {
			if (ndef == null) {
				shotToast("格式化数据开始");
				//Ndef格式类
				NdefFormatable format = NdefFormatable.get(tag);
				format.connect();
				format.format(ndefMessage);
			} else {
				shotToast("写入数据开始");
				//数据的写入过程一定要有连接操作
				ndef.connect();
				ndef.writeNdefMessage(ndefMessage);
			}
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			shotToast("IO异常,读写失败");
		} catch (FormatException e) {
			e.printStackTrace();
			shotToast("格式化异常,读写失败");
		} catch (NullPointerException e) {
			shotToast("格NullPointerException异常,读写失败");
		}catch (IllegalStateException e){
			shotToast("Close other technology first!");
		}
		return false;
	}
	

/**
 * 读取NFC标签文本数据
 */
private void readNfcTag(Intent intent) {
	if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())||
			NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
		Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
				NfcAdapter.EXTRA_NDEF_MESSAGES);
		NdefMessage msgs[] = null;
		int contentSize = 0;
		if (rawMsgs != null) {
			msgs = new NdefMessage[rawMsgs.length];
			for (int i = 0; i < rawMsgs.length; i++) {
				msgs[i] = (NdefMessage) rawMsgs[i];
				contentSize += msgs[i].toByteArray().length;
			}
		}
		try {
			if (msgs != null) {
				print(msgs.length+" 长度");
				NdefRecord record = msgs[0].getRecords()[0];
				String textRecord = parseTextRecord(record);
				mTagText += textRecord + "\n\ntext\n" + contentSize + " bytes";
				print(mTagText);
			}
		} catch (Exception e) {
		}
	}
}

2,扇区读写

M1扇区默认是没有密码的,但有部分人闲不住要把密码改了,因此认证过程要加密码,一般认证KeyA就行。普通卡16个扇区64块,第一个扇区等闲不能操作。每个扇区4块,从0数起,第二扇区第一块索引就是8,每个扇区前3块存数据最后一块一般存密码。实例代码读的是2扇区8块。

	/**
	 * 扇区写
	 * @param tag
	 * @param sectorIndex  扇区索引  一般16个扇区 64块
	 * @return
	 */
	public boolean writeTAG(Tag tag,int sectorIndex) {
		MifareClassic mfc = MifareClassic.get(tag);
		try {
			mfc.connect();
			if (mfc.authenticateSectorWithKeyA(sectorIndex, new byte[]{0x42,0x53,0x4B, (byte) sectorIndex,0x4C,0x53})) {   //已知密码认证    r
				// the last block of the sector is used for KeyA and KeyB cannot be overwritted
				int block = mfc.sectorToBlock(sectorIndex);
				mfc.writeBlock(block, "sgn-old000000000".getBytes());
				mfc.close();
				shotToast("旧卡 写入成功");
				return true;
			}else if(mfc.authenticateSectorWithKeyA(sectorIndex, MifareClassic.KEY_NFC_FORUM)){     //新卡 未设密码认证  r
				int block = mfc.sectorToBlock(sectorIndex);
				mfc.writeBlock(block, "SGN-new000000000".getBytes());
				mfc.close();
				shotToast("新卡 写入成功");
			} else{
				shotToast("未认证");
			}
		} catch (IOException e) {
			e.printStackTrace();
			shotToast("扇区连接异常");

			try {
				mfc.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
		return false;
	}


	/**
	 * 读扇区
	 * @return
	 */
	private String readTag(Tag tag,MifareClassic mfc,int sectorIndex){
		for (String tech : tag.getTechList()) {
			System.out.println("------------"+tech);
		}
		//读取TAG
		try {
			String metaInfo = "";
			//Enable I/O operations to the tag from this TagTechnology object.
			mfc.connect();
			int type = mfc.getType();//获取TAG的类型
			int sectorCount = mfc.getSectorCount();//获取TAG中包含的扇区数
			String typeS = "";
			switch (type) {
				case MifareClassic.TYPE_CLASSIC:
					typeS = "TYPE_CLASSIC";
					break;
				case MifareClassic.TYPE_PLUS:
					typeS = "TYPE_PLUS";
					break;
				case MifareClassic.TYPE_PRO:
					typeS = "TYPE_PRO";
					break;
				case MifareClassic.TYPE_UNKNOWN:
					typeS = "TYPE_UNKNOWN";
					break;
			}
			metaInfo += "卡片类型:" + typeS + "\n共" + sectorCount + "个扇区\n共" 	+ mfc.getBlockCount() + "个块\n存储空间: " + mfc.getSize() + "B\n";
			int blockIndex;
			if (mfc.authenticateSectorWithKeyA(sectorIndex, new byte[]{0x42,0x53,0x4B, (byte) sectorIndex,0x4C,0x53}) ) {
				blockIndex = mfc.sectorToBlock(sectorIndex);
				byte[] data = mfc.readBlock(blockIndex);
				metaInfo += "旧卡 Block " + blockIndex + " : " + new String(data) + "\n";
			}else if( mfc.authenticateSectorWithKeyA(sectorIndex, MifareClassic.KEY_NFC_FORUM)){
				blockIndex = mfc.sectorToBlock(sectorIndex);
				byte[] data = mfc.readBlock(blockIndex);
				metaInfo += "新卡 Block " + blockIndex + " : " + new String(data) + "\n";

			}else {
				metaInfo += "Sector " + sectorIndex + ":验证失败\n";
			}
			return metaInfo;
		} catch (Exception e) {
			Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
			e.printStackTrace();
		} finally {
			if (mfc != null) {
				try {
					mfc.close();
				} catch (IOException e) {
					Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG)
							.show();
				}
			}
		}
		return null;
	}

3 CPU卡读写 重头戏

先直接上代码,看完代码在说吧,搞这个有点心力疲惫。
下面是一个写的完全流程,if的嵌套我承认有点low,但有助于流程理解。
这里要说下外部认证过程:
devices -----获取4字节随机数---------------------> cpu 卡
devices <--------随机数+90 00--------------------- cpu 卡
四个字节随机数+四个字节0 使用密钥进行DES加密,如果是8个随机数DES3加密。
将命令00 82 00 00 08 以及加密后的随机数取前8位 7f cf 90 a0 5b 9c f1 73发送
devices ----00 82 00 00 08 7f cf 90 a0 5b 9c f1 73–>cpu 卡
devices <------------- 90 00---------------------------- cpu 卡

/**
 * Description    :  cpu卡写的工具类  命令返回90 00 表示成功
 * CreateAuthor: Cannan
 * CreateTime   : 2018/9/22     18:53
 * Project          : TestNFC
 */

public class NfcCpuUtils {

	/**
	 * 1. 在“COS命令框”输入“00A40000023F00”,然后点击“发送命令”,进入主目录
	 */
	private  final byte[] CMD_START = new byte[]{0x00, (byte) 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00};    //6f,15,84,e,31,50,41,59,2e,53,59,53,2e,44,44,46,30,31,a5,3,88,1,1,90,0,
	/**
	 * 2. 复合外部认证(秘钥:FFFFFFFFFFFFFFFF,秘钥标识号:00)
	 */
	private  byte[] CMD_KEY = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
	/**
	 * 2.1 获取4位 随机码 {0x00, (byte) 0x84, 0x00, 0x00, 0x04}
	 */
	private final byte[] CMD_GET_RANDOM = {0x00, (byte) 0x84, 0x00, 0x00, 0x04};

	private  final byte[] CMD_DEL = {(byte) 0x80, 0x0E, 0x00, 0x00, 0x00};     //3.删除主目录下的所有文件:800E000000(注意:这个命令会删除主目录下的所有文件)

	//	4. 建立外部认证秘钥    4.1选择根目录(00A4000000)
	// 4.2建密钥文件 (80 E0 00 00 07 3F 00 B0 01 F0 FF FF
	// 4.3创建外部认证密钥 (80 D4 01 00 0D 39 F0F0 AA 55 FFFFFFFFFFFFFFFF)
	private  final byte[] CMD_CREATE_DIR = {0x00, (byte) 0xA4, 0x00, 0x00, 0x02,0x3f,0x00};
	private  final byte[] CMD_CREATE_KEY = {(byte) 0x80, (byte) 0xE0, 0x00, 0x00, 0x07, 0x3F, 0x00, (byte) 0xB0, 0x01, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF};
	private  final byte[] CMD_CREATE_OUT_KEY = {(byte) 0x80, (byte) 0xD4, (byte) 0x01, (byte) 0x00, (byte) 0x0D, (byte)0x39, (byte) 0xF0, (byte) 0xF0, (byte) 0xAA
			, (byte) 0x55, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
	//5 建立访问自定义文件的密钥文件
	private  final byte[] CMD_ACCESS = {(byte) 0x80, (byte) 0xE0, (byte) 0x00, (byte) 0x01, (byte) 0x07, (byte) 0x3F, (byte) 0x01, (byte) 0x8F, (byte) 0x95, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF};
	// 填充密钥123456
	private  final byte[] CMD_ACCESS_INTO = {(byte) 0x80, (byte) 0xD4, (byte) 0x01, (byte) 0x01, (byte) 0x08, (byte) 0x3A, (byte) 0xF0, (byte) 0xEF, (byte) 0x44, (byte) 0x55, (byte) 0x12, (byte) 0x34, (byte) 0x56};
	//6. 创建自定义文件,标识为005(80E000050728000FF4F4FF02)
	private  final byte[] CMD_ACCESS_FILE = {(byte) 0x80, (byte) 0xE0, (byte) 0x00, (byte) 0x05, (byte) 0x07, (byte) 0x28, (byte) 0x00, (byte) 0x0F, (byte) 0xF4, (byte) 0xF4, (byte) 0xFF, (byte) 0x02};
	//7.写数据到文件标识为0005的文件
	//7.1选中该文件(00A40000020005)
	//	7.2写数据“112233445566”到该文件(00D6000006112233445566)
	private  final byte[] CMD_ACCESS_FILE_CHOOICE = {(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x05};
	private final byte[] CMD_ACCESS_FILE_WRITE = {(byte) 0x00, (byte) 0xD6, (byte) 0x00, (byte) 0x00, (byte) 0x06, (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x44, (byte) 0x55, (byte) 0x66};

	// 声明ISO-DEP协议的Tag操作实例
	private final IsoDep tag;

	public NfcCpuUtils(IsoDep tag) throws IOException {
		// 初始化ISO-DEP协议的Tag操作类实例
		this.tag = tag;
		tag.setTimeout(5000);
		tag.connect();
	}

	public byte[] wirte() throws IOException {
		byte[] resp = tag.transceive(CMD_START);  //1 进入主目录
		if (checkRs(resp)) {
			print("1 进入主目录成功");
			resp = tag.transceive(CMD_GET_RANDOM); //2 获取随机码
			if (checkRs(resp)) {
				print("2 获取随机码");
				byte[] random = {resp[0], resp[1], resp[2], resp[3], 0x00, 0x00, 0x00, 0x00};//3 随机码4个字节+4个字节0
				byte[] desKey;
				try {
					desKey = encrypt(random, CMD_KEY); //4 生产加密后的随机码
					print("3 生产加密后的随机码");
					printByte(desKey);
				} catch (Exception e) {
					e.printStackTrace();
					desKey = null;
				}
				 //00 82 00 00 08 7f cf 90 a0 5b 9c f1 73
				if (desKey != null && desKey.length > 8) {
					byte[] respondKey = {0x00, (byte) 0x82, 0x00, 0x00, 0x08, desKey[0], desKey[1], desKey[2], desKey[3], desKey[4], desKey[5], desKey[6], desKey[7]};
					print("4 生产加密后的随机码命令");
					printByte(respondKey);
					resp = tag.transceive(respondKey); //5 将加密后的随机码发送,注意此处第四字节表示密码标识符00,
				}              
				if (checkRs(resp)) {
					print("5 外部认证成功");
					resp = tag.transceive(CMD_DEL);
					if (checkRs(resp)) {
						print("6 删除目录成功");
						resp = tag.transceive(CMD_CREATE_DIR);
						if (checkRs(resp)) {
							print("7 选择目录");
							resp = tag.transceive(CMD_CREATE_KEY);
							if (checkRs(resp)) {
								print("8 建立目录");
								resp = tag.transceive(CMD_CREATE_OUT_KEY);
								if (checkRs(resp)) {
									print("9 创建外部认证密钥成功");
									resp = tag.transceive(CMD_ACCESS);
									if (checkRs(resp)) {
										print("10 建立访问自定义文件的密钥文件成功");
										resp = tag.transceive(CMD_ACCESS_INTO); //11 填充密钥123456
										if (checkRs(resp)) {
											print("11 填充密钥123456成功");
											resp = tag.transceive(CMD_ACCESS_FILE); //12  创建自定义文件,标识为005
											if (checkRs(resp)) {
												print("12  创建自定义文件,标识为005成功");
												resp = tag.transceive(CMD_ACCESS_FILE_CHOOICE);   // 13  选中该文件0005
												if (checkRs(resp)) {
													print(" 13  选中该文件0005成功");
													resp = tag.transceive(CMD_ACCESS_FILE_WRITE);  //14 写数据“112233445566”到该文件
													if (checkRs(resp)) {       //15 应该有关闭连接
														print("14 写数据“112233445566”到该文件成功");
														return "01".getBytes();
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	private boolean checkRs(byte[] resp) {
		String r = printByte(resp);
		Log.i("---------", "response " + r);
		int status = ((0xff & resp[resp.length - 2]) << 8) | (0xff & resp[resp.length - 1]);
		return status == 0x9000;
	}

	private String printByte(byte[] data) {
		StringBuffer bf = new StringBuffer();

		for (byte b : data) {
			bf.append(Integer.toHexString(b & 0xFF));
			bf.append(",");
		}
		Log.i("TAG", bf.toString());
		return bf.toString();
	}

	private void print(String msg) {
		Log.i("TAG", msg);
	}

	/**
	 * Description 根据键值进行加密
	 * 随机码4个字节+4个字节0
	 *
	 * @param data
	 * @param key  加密键byte数组
	 * @return
	 * @throws Exception
	 */
	public byte[] encrypt(byte[] data, byte[] key) throws Exception {
	}
}

注意接收和处理返回的信息,CPU卡常用的APDU指令
参考文献:很多博客,记不得了,RFID多功能读卡器说明
https://blog.csdn.net/qq_34075348/article/details/77877306
FMCOS2.0用户手册 50-70
如果需要源码:下载地址https://download.csdn.net/download/sgn5200/10688898

你可能感兴趣的:(android-细节,Android,NFC,CPU卡)