赞
踩
NFC 标签读写
NFC 扇区读写
NFC 文件读写
<uses-feature
android:name="android.hardware.nfc"
android:required="true"/>
<uses-permission android:name="android.permission.NFC"/>
<activity android:name=".ReadTextActivity" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<data android:mimeType="text/plain"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
在Activity#onCreate()注册,在Activity#onResume()开启前台调度系统,在Activity#onPause退出前台调度。
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()}};// 允许扫描的标签类型 }
@Override
protected void onResume() {
super.onResume();
//开启前台调度系统
nfcAdapter.enableForegroundDispatch(this, pendingIntent, mWriteTagFilters, mTechLists);
}
@Override
protected void onPause() {
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
给伪代码,详细见下面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)); } }
/** * 写标签 * @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) { } } }
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; }
先直接上代码,看完代码在说吧,搞这个有点心力疲惫。
下面是一个写的完全流程,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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。