版權聲明:本文為博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/sgn5200/article/details/82855478
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 文件讀寫
權限
<uses-feature
android:name="android.hardware.nfc"
android:required="true"/>
<uses-permission android:name="android.permission.NFC"/>
1
2
3
4
5
<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>
1
2
3
4
5
6
7
8
初始化
在Activity#onCreate()注冊,在Activity#onResume()開啟前台調度系統,在Activity#onPause退出前台調度。
1
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()}};// 允許掃描的標簽類型
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2 onResume( )
@Override
protected void onResume() {
super.onResume();
//開啟前台調度系統
nfcAdapter.enableForegroundDispatch(this, pendingIntent, mWriteTagFilters, mTechLists);
}
1
2
3
4
5
6
3 onPause()
@Override
protected void onPause() {
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
1
2
3
4
5
4 NFC設備刷卡時觸發 onNewIntent(Intent)
給偽代碼,詳細見下面3點分解
1
@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) {
}
}
}
1
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;
}
99
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 {
}
}
https://blog.csdn.net/sgn5200/article/details/82855478
注意接收和處理返回的信息,CPU卡常用的APDU指令
參考文獻:很多博客,記不得了,RFID多功能讀卡器說明
https://blog.csdn.net/qq_34075348/article/details/77877306
FMCOS2.0用戶手冊 50-70
如果需要源碼:下載地址https://download.csdn.net/download/sgn5200/10688898
————————————————
版權聲明:本文為CSDN博主「豆湯包谷飯」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/sgn5200/article/details/82855478