一得运、NFC概述
NFC(Near Field Communication)也叫近距離無線通信凫碌,是一項(xiàng)無線技術(shù)管引。 NFC由非接觸式射頻識(shí)別(RFID)及互聯(lián)互通技術(shù)整合演變而來,在單一芯片上結(jié)合感應(yīng)式讀卡器货抄、感應(yīng)式卡片和點(diǎn)對(duì)點(diǎn)的功能漆改,利用移動(dòng)終端能在短距離內(nèi)與兼容設(shè)備進(jìn)行識(shí)別和數(shù)據(jù)交換沾瓦。
NFC具有距離近满着、帶寬高、能耗低等特點(diǎn)贯莺。適用于一些敏感信息或個(gè)人數(shù)據(jù)的傳輸?shù)确缋诎踩陨暇哂袃?yōu)勢(shì),NFC與現(xiàn)有非接觸智能卡技術(shù)兼容缕探,已經(jīng)成為得到越來越多主要廠商支持的正式標(biāo)準(zhǔn)魂莫。利用NFC功能绍坝,可以實(shí)現(xiàn)消費(fèi)衅金、門禁等多種應(yīng)用,代替卡包里的多種卡片顺少,如移動(dòng)支付鲸沮、電子票務(wù)琳骡、門禁、移動(dòng)身份識(shí)別讼溺、防偽等應(yīng)用楣号。
參考《深入理解Android:Wi-Fi、NFC和GPS卷》一書:該技術(shù)最早由Philips和Sony兩家公司于2002年年末聯(lián)合推出怒坯,從原理上說炫狱,NFC和WiFi類似,二者都利用無線射頻技術(shù)來實(shí)現(xiàn)設(shè)備之間的通信剔猿。但是區(qū)別是视译,NFC的工作頻率為13.56MHz,有效距離為<4cm归敬。
所以這在很大程度上要求使用NFC的雙方設(shè)備具備相當(dāng)高的信任程度酷含,不然不可能使其靠近自己的設(shè)備,這在一定程度上表明NFC技術(shù)的安全性汪茧。
接下來說一下RFID即無線射頻識(shí)別技術(shù)椅亚,而NFC技術(shù)起源于RFID技術(shù),RFID有低頻,高頻(13.56MHz)和超高頻工作頻率舱污,.在應(yīng)用領(lǐng)域:RFID更多的應(yīng)用在生產(chǎn)呀舔,物流,跟蹤和資產(chǎn)管理上扩灯,而NFC則工作在門禁媚赖,公交卡霜瘪,手機(jī)支付等領(lǐng)域。在工作模式:NFC同時(shí)支持讀寫模式和卡模式惧磺。而在RFID中颖对,讀卡器和非接觸卡是獨(dú)立的兩個(gè)實(shí)體,不能切換磨隘。
二惜互、NFC應(yīng)用
NFC設(shè)備可以用作非接觸式智能卡、智能卡的讀寫器終端以及設(shè)備對(duì)設(shè)備的數(shù)據(jù)傳輸鏈路琳拭。NFC應(yīng)用可以分為四個(gè)基本類型:
1.接觸、完成描验。諸如門禁白嘁、活動(dòng)檢票之類的應(yīng)用,用戶只需將儲(chǔ)存有票證或門禁代碼的設(shè)備靠近閱讀器即可膘流。還可用于簡單的數(shù)據(jù)擷取應(yīng)用絮缅。
2.接觸、確認(rèn)呼股。移動(dòng)付費(fèi)之類的應(yīng)用耕魄,如食堂消費(fèi)、交通工具支付彭谁,用戶必須輸入密碼確認(rèn)交易吸奴,或者僅接受交易。
3.接觸缠局、連接则奥。將兩臺(tái)支持NFC的設(shè)備鏈接,即可進(jìn)行點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)數(shù)據(jù)傳輸狭园,例如下載音樂读处、交換圖像或同步處理通信錄等。
4.接觸唱矛、探索罚舱。NFC設(shè)備可能提供不止一種功能,消費(fèi)者可以探索了解設(shè)備的功能绎谦,找出NFC設(shè)備潛在的功能與服務(wù)管闷。
三、NFC工作模式
NFC支持如下3種工作模式:讀卡器模式(Reader/writer mode)燥滑、仿真卡模式(Card Emulation Mode)渐北、點(diǎn)對(duì)點(diǎn)模式(P2P mode)。
下來分別看一下這三種模式:
1铭拧、讀卡器模式:
數(shù)據(jù)在NFC芯片中赃蛛,可以簡單理解成“刷標(biāo)簽”恃锉。本質(zhì)上就是通過支持NFC的手機(jī)或其它電子設(shè)備從帶有NFC芯片的標(biāo)簽、貼紙呕臂、名片等媒介中讀寫信息破托。通常NFC標(biāo)簽是不需要外部供電的。當(dāng)支持NFC的外設(shè)向NFC讀寫數(shù)據(jù)時(shí)歧蒋,它會(huì)發(fā)送某種磁場(chǎng)土砂,而這個(gè)磁場(chǎng)會(huì)自動(dòng)的向NFC標(biāo)簽供電。
2谜洽、仿真卡模式:
數(shù)據(jù)在支持NFC的手機(jī)或其它電子設(shè)備中萝映,可以簡單理解成“刷手機(jī)”。本質(zhì)上就是將支持NFC的手機(jī)或其它電子設(shè)備當(dāng)成借記卡阐虚、公交卡序臂、門禁卡等IC卡使用∈凳基本原理是將相應(yīng)IC卡中的信息憑證封裝成數(shù)據(jù)包存儲(chǔ)在支持NFC的外設(shè)中 奥秆。
在使用時(shí)還需要一個(gè)NFC射頻器(相當(dāng)于刷卡器)。將手機(jī)靠近NFC射頻器咸灿,手機(jī)就會(huì)接收到NFC射頻器發(fā)過來的信號(hào)构订,在通過一系列復(fù)雜的驗(yàn)證后,將IC卡的相應(yīng)信息傳入NFC射頻器避矢,最后這些IC卡數(shù)據(jù)會(huì)傳入NFC射頻器連接的電腦悼瘾,并進(jìn)行相應(yīng)的處理(如電子轉(zhuǎn)帳、開門等操作)谷异。
3分尸、點(diǎn)對(duì)點(diǎn)模式:
該模式與藍(lán)牙、紅外差不多歹嘹,用于不同NFC設(shè)備之間進(jìn)行數(shù)據(jù)交換箩绍,不過這個(gè)模式已經(jīng)沒有有“刷”的感覺了。其有效距離一般不能超過4厘米尺上,但傳輸建立速度要比紅外和藍(lán)牙技術(shù)快很多材蛛,傳輸速度比紅外塊得多,如過雙方都使用Android4.2怎抛,NFC會(huì)直接利用藍(lán)牙傳輸卑吭。這種技術(shù)被稱為Android Beam。所以使用Android Beam傳輸數(shù)據(jù)的兩部設(shè)備不再限于4厘米之內(nèi)马绝。
點(diǎn)對(duì)點(diǎn)模式的典型應(yīng)用是兩部支持NFC的手機(jī)或平板電腦實(shí)現(xiàn)數(shù)據(jù)的點(diǎn)對(duì)點(diǎn)傳輸豆赏,例如,交換圖片或同步設(shè)備聯(lián)系人。因此掷邦,通過NFC白胀,多個(gè)設(shè)備如數(shù)字相機(jī),計(jì)算機(jī)抚岗,手機(jī)之間或杠,都可以快速連接,并交換資料或者服務(wù)宣蔚。
下面對(duì)比一下NFC向抢、藍(lán)牙和紅外之間的差異:
對(duì)比項(xiàng) NFC 藍(lán)牙 紅外 網(wǎng)絡(luò)類型 點(diǎn)對(duì)點(diǎn) 單點(diǎn)對(duì)多點(diǎn) 點(diǎn)對(duì)點(diǎn) 有效距離 <=0.1m <=10m,最新的藍(lán)牙4.0有效距離可達(dá)100m 一般在1m以內(nèi)胚委,熱技術(shù)連接挟鸠,不穩(wěn)定 傳輸速度 最大424kbps 最大24Mbps 慢速115.2kbps,快速4Mbps 建立時(shí)間 <0.1s 6s 0.5s 安全性 安全亩冬,硬件實(shí)現(xiàn) 安全兄猩,軟件實(shí)現(xiàn) 不安全,使用IRFM時(shí)除外 通信模式 主動(dòng)-主動(dòng)/被動(dòng) 主動(dòng)-主動(dòng) 主動(dòng)-主動(dòng) 成本 低 中 低
四鉴未、Android實(shí)現(xiàn)仿真卡模式(Card Emulation Mode)
這里通過NFC的仿真卡模式(Card Emulation Mode)實(shí)現(xiàn)了公司所有讀頭產(chǎn)品相關(guān)配置和固件升級(jí)功能;
1:通過手機(jī)NFC模擬配置卡功能來配置讀頭相關(guān)參數(shù)鸠姨;
2:通過手機(jī)NFC模擬電子工牌铜秆,實(shí)現(xiàn)刷手機(jī)開門功能;
3:通過手機(jī)NFC模擬卡片升級(jí)讶迁,升級(jí)讀頭固件等功能连茧;
NFC具體細(xì)節(jié)可參閱博文這里不做過多贅述,直接上代碼:
1:NFC權(quán)限
<uses-permission android:name="android.permission.NFC" />
<!--API 9 設(shè)備可以使用近場(chǎng)通信(NFC)進(jìn)行通信巍糯。-->
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<!--API 19 該設(shè)備支持基于主機(jī)的NFC卡仿真啸驯。-->
<uses-feature
android:name="android.hardware.nfc.hcef"
android:required="true" />
<!--API 24 該設(shè)備支持基于主機(jī)的NFC-F卡仿真。-->
<uses-feature
android:name="android.hardware.nfc.hce"
android:required="true" />
2:Service implementation
Android 4.4帶有一個(gè)便利Service類祟峦,可以作為實(shí)現(xiàn)HCE服務(wù)的基礎(chǔ):HostApduService類罚斗。
processCommandApdu() 只要NFC閱讀器向您的服務(wù)發(fā)送應(yīng)用程序協(xié)議數(shù)據(jù)單元(APDU),就會(huì)調(diào)用此方法宅楞。APDU也在ISO / IEC 7816-4規(guī)范中定義针姿。APDU是NFC讀取器和HCE服務(wù)之間交換的應(yīng)用程序級(jí)數(shù)據(jù)包。該應(yīng)用程序級(jí)協(xié)議是半雙工的:NFC讀取器將向您發(fā)送命令A(yù)PDU厌衙,它將等待您發(fā)送響應(yīng)APDU作為回報(bào)距淫。
注: ISO / IEC 7816-4規(guī)范還定義了多個(gè)邏輯信道的概念,您可以在不同的邏輯信道上進(jìn)行多個(gè)并行APDU交換婶希。然而榕暇,Android的HCE實(shí)現(xiàn)僅支持單個(gè)邏輯通道,因此只有APDU的單線程交換。
如前所述彤枢,Android使用AID來確定讀者想要與之通信的HCE服務(wù)狰晚。通常,NFC讀取器發(fā)送到您的設(shè)備的第一個(gè)APDU是“SELECT AID”APDU; 此APDU包含讀者想要與之交談的AID堂污。Android從APDU中提取該AID家肯,將其解析為HCE服務(wù),然后將該APDU轉(zhuǎn)發(fā)到已解析的服務(wù)盟猖。
onDeactivated() 卡片移走或斷開連接時(shí)調(diào)用讨衣,并帶有一個(gè)參數(shù),指示兩者中的哪一個(gè)發(fā)生了式镐。
package com.roy.www.nfc_configcard.service;
import android.content.Context;
import android.content.Intent;
import android.nfc.cardemulation.HostApduService;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import androidx.annotation.RequiresApi;
import com.roy.www.nfc_configcard.ui.activity.ContentActivity;
import com.roy.www.nfc_configcard.ui.activity.NFCUpdateMcuActivity;
import com.roy.www.nfc_configcard.utils.ActivityUtil;
import com.roy.www.nfc_configcard.utils.Aes128EcbUtils;
import com.roy.www.nfc_configcard.utils.ByteUtils;
import com.roy.www.nfc_configcard.utils.HexDump;
import com.roy.www.nfc_configcard.utils.MmkvUtils;
import java.util.Arrays;
import javax.crypto.Cipher;
/**
* Created by Roy.lee
* On 2022/6/27
* Email: 631934797@qq.com
* Description: 仿真卡服務(wù)
*/
public class CardEmulationService extends HostApduService {
private static final String TAG = CardEmulationService.class.getSimpleName();
private static final String SEND = " : ==> ";
private static final String RECE = " : <== ";
private long stopTime;
private static Handler mHandler;
private static StringBuilder mStrBuilder;
private static int SELECT_FILE = 0;
private byte[] RANDOM_NUMBER ;
private boolean IS_EX_AUTH = false;
private boolean IS_IN_AUTH = false;
public static final int MESSAGE_UPDATE_PROGRESS = 0;
public static final int READ_CARD = 1;
public static final int DISCONNECT = 2;
public static boolean isUpdate = false;
public static byte[] MCU_BUF;
public static byte[] MCU_INFO = new byte[59];
public static byte[] VERSION_BYTES = new byte[48];
public static byte[] MCU_SIZE = new byte[4] ;
public static byte[] SUM = new byte[4];
public static byte EOR;
public static int cnot = 0;
public static Intent newHCEServiceIntent(Context context){
Intent hceIntent = new Intent(context, CardEmulationService.class);
return hceIntent;
}
public static Intent newHCEServiceIntent(Context context, Handler handler, StringBuffer mBuf){
Intent hceIntent = new Intent(context, CardEmulationService.class);
mHandler = handler;
mSb = mBuf;
return hceIntent;
}
public static void setHandler(Handler handler, StringBuilder builder){
mHandler = handler;
mStrBuilder = builder;
}
@Override
public void onCreate() {
Log.i(TAG, "... CardEmulationService on create ...");
logAppend(TAG + " : ... CardEmulationService on create ...");
super.onCreate();
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
Log.i(TAG, RECE + ByteUtils.toHexString(commandApdu));
logAppend(TAG + RECE + ByteUtils.toHexString(commandApdu));
String cmdApdu = ByteUtils.toHexString(ByteUtils.getSubArray(commandApdu,0,2));
Log.i(TAG, "cmdApdu : " + cmdApdu);
if (cmdApdu.equals(ApduCommands.CMD_00A4)){//選擇文件
return selectFileAndAid(commandApdu);
}
else if (cmdApdu.equals(ApduCommands.CMD_00B0)){//讀取數(shù)據(jù)
return readBinary(commandApdu);
}
else if (cmdApdu.equals(ApduCommands.CMD_0084)){//獲取隨機(jī)數(shù)
return getChallenge(commandApdu);
}
else if (cmdApdu.equals(ApduCommands.CMD_0082)){//外部認(rèn)證
return externalAuth(commandApdu);
}
else if (cmdApdu.equals(ApduCommands.CMD_0088)){//內(nèi)部認(rèn)證
return internalAuth(commandApdu);
}
else if (cmdApdu.equals(ApduCommands.CMD_800E)){//擦除當(dāng)前目錄文件
return eraseDF(commandApdu);
}
else if (cmdApdu.equals(ApduCommands.CMD_80E0)){//創(chuàng)建文件
return createFile(commandApdu);
}
else if (cmdApdu.equals(ApduCommands.CMD_80D4)){//寫KEY
return writeKey(commandApdu);
}
else if (cmdApdu.equals(ApduCommands.CMD_00D6)){//寫二進(jìn)制文件
return updateBinary(commandApdu);
}else{
return ApduCommands.SW_6300;
}
}
@Override
public void onDeactivated(int reason) {
Log.i(TAG, "onDeactivated(). Reason: " + reason);
logAppend(TAG + "onDeactivated(). Reason: " + reason);
SELECT_FILE = 0;
IS_EX_AUTH = false;
IS_IN_AUTH = false;
stopTime = System.currentTimeMillis();
mHandler.sendEmptyMessage(DISCONNECT);
}
/**
* 選文件和AID
* @param commandApdu
*/
private byte[] selectFileAndAid(byte[] commandApdu) {
if (Arrays.equals(commandApdu, HexDump.hexStringToByteArray(ApduCommands.SELECT_MCU_AID))
&& ActivityUtil.isForeground(this, NFCUpdateMcuActivity.class.getName())
&& isUpdate){//
sendUpdateMessage(0);
logAppend(TAG + " : ... MCU AID ...");
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_9000));
cnot++;
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, HexDump.hexStringToByteArray(ApduCommands.SELECT_FILE_FFFF))){
//TODO 返回固件信息
logAppend(TAG + SEND + HexDump.toHexString(MCU_INFO));
return MCU_INFO;
}
else if (Arrays.equals(commandApdu, HexDump.hexStringToByteArray(ApduCommands.SELECT_FILE_0000))){
sendUpdateMessage(100);
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_9000));
logAppend(TAG + " : 升級(jí)成功");
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, HexDump.hexStringToByteArray(ApduCommands.SELECT_FILE_1111))){
logAppend(TAG + " : 校驗(yàn)失敗");
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_CONFIG_AID))
&& ActivityUtil.isForeground(this,ContentActivity.class.getName())){
logAppend(TAG + " : ... Config Aid ...");
byte[] rand = HexDump.getRand(4);
ApduCommands.initDesKey(rand);
logAppend(TAG + SEND + ByteUtils.toHexString(ByteUtils.concatenate( rand,ApduCommands.SW_9000)));
return ByteUtils.concatenate(rand,ApduCommands.SW_9000);
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_CF01))){
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF01))){
SELECT_FILE = 1;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF02))){
SELECT_FILE = 2;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF03))){
SELECT_FILE = 3;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF04))){
SELECT_FILE = 4;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF05))){
SELECT_FILE = 5;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF06))){
SELECT_FILE = 6;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF10))){
SELECT_FILE = 10;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF11))){
SELECT_FILE = 11;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF12))){
SELECT_FILE = 12;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_EF13))){
SELECT_FILE = 13;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (Arrays.equals(commandApdu, ByteUtils.toByteArray(ApduCommands.SELECT_FILE_3F00))){
SELECT_FILE = 14;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else {
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_6A82));
return ApduCommands.SW_6A82;
}
}
/**
* 讀取數(shù)據(jù)
* @param commandApdu
* @return
*/
private byte[] readBinary(byte[] commandApdu) {
if (commandApdu.length == 6) {
int offset = HexDump.bigBytesToInt(HexDump.getSubArray(commandApdu,2,2));
int len = HexDump.bigBytesToInt(HexDump.getSubArray(commandApdu,4,2));
if ((offset+len ) > CardEmulationService.MCU_BUF.length){
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6982));
return ApduCommands.SW_6982;
}
sendUpdateMessage((offset * 100) / CardEmulationService.MCU_BUF.length);
byte[] tempBytes = HexDump.getSubArray(CardEmulationService.MCU_BUF,offset,len);
logAppend(TAG + SEND + HexDump.toHexString(HexDump.concatenate(tempBytes,ApduCommands.SW_9000)));
return HexDump.concatenate(tempBytes,ApduCommands.SW_9000);
}
else if (commandApdu.length == 5 && Arrays.equals(HexDump.getSubArray(commandApdu,0,2), ByteUtils.toByteArray(ApduCommands.READ_BINARY))){
int len = commandApdu[commandApdu.length-1]&0xFF;
int offset = HexDump.bigBytesToInt(HexDump.getSubArray(commandApdu,2,2));
byte[] tempBytes = new byte[len];
if (SELECT_FILE == 1){
if (len <= sEF01File.toFileStream(sEF01File).length){
tempBytes = HexDump.getSubArray(sEF01File.toFileStream(sEF01File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 2){
if (len <= sEF02File.toFileStream(sEF02File).length){
tempBytes = HexDump.getSubArray(sEF02File.toFileStream(sEF02File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 3){
if (len <= sEF03File.toFileStream(sEF03File).length){
tempBytes = HexDump.getSubArray(sEF03File.toFileStream(sEF03File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 4){
if (len <= sEF04File.toFileStream(sEF04File).length){
tempBytes = HexDump.getSubArray(sEF04File.toFileStream(sEF04File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 5){
if (len <= sEF05File.toFileStream(sEF05File).length){
tempBytes = HexDump.getSubArray(sEF05File.toFileStream(sEF05File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 6){
if (len <= sEF06File.toFileStream(sEF06File).length){
tempBytes = HexDump.getSubArray(sEF06File.toFileStream(sEF06File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 10){
if (len <= sEF10File.toFileStream(sEF10File).length){
tempBytes = HexDump.getSubArray(sEF10File.toFileStream(sEF10File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 11){
if (len <= sEF11File.toFileStream(sEF11File).length){
tempBytes = HexDump.getSubArray(sEF11File.toFileStream(sEF11File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 12){
if (len <= sEF12File.toFileStream(sEF12File).length){
tempBytes = HexDump.getSubArray(sEF12File.toFileStream(sEF12File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
else if (SELECT_FILE == 13){
if (len <= sEF13File.toFileStream(sEF13File).length){
tempBytes = HexDump.getSubArray(sEF13File.toFileStream(sEF13File),offset,len);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
logAppend(TAG + " : 讀取長度錯(cuò)誤");
return ApduCommands.SW_6700;
}
}
logAppend(TAG + SEND + ByteUtils.toHexString(ByteUtils.concatenate(tempBytes,ApduCommands.SW_9000)));
return ByteUtils.concatenate(tempBytes,ApduCommands.SW_9000);
} else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6A81));
return ApduCommands.SW_6A81;
}
}
/**
* 獲取隨機(jī)數(shù)
* @param commandApdu
* @return
*/
private byte[] getChallenge(byte[] commandApdu) {
if (commandApdu.length == 5){
int len = commandApdu[4] & 0xFF;
if (len == 4 || len == 8){
RANDOM_NUMBER = HexDump.getRand(len);
logAppend(TAG + SEND + HexDump.toHexString(HexDump.concatenate(RANDOM_NUMBER,ApduCommands.SW_9000)));
return HexDump.concatenate(RANDOM_NUMBER,ApduCommands.SW_9000);
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6700));
return ApduCommands.SW_6700;
}
} else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6A81));
return ApduCommands.SW_6A81;
}
}
/**
* 外部認(rèn)證
* @param commandApdu
* @return
*/
private byte[] externalAuth(byte[] commandApdu) {
int len = commandApdu[4]&0xFF;
if (commandApdu.length == (len+5) && Arrays.equals(ByteUtils.getSubArray(commandApdu,0,4),ByteUtils.toByteArray(ApduCommands.EXTERNAL_AUTH))){
byte[] subArray = ByteUtils.getSubArray(commandApdu, 5, len);
byte[] desDecrypt = Aes128EcbUtils.DESede(subArray,
ApduCommands.EX_AUTH_KEY,
Cipher.DECRYPT_MODE);
if (Arrays.equals(RANDOM_NUMBER,desDecrypt)){
IS_EX_AUTH = true;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}else {
IS_EX_AUTH = false;
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_63CF));
return ApduCommands.SW_63CF;
}
} else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_9302));
return ApduCommands.SW_9302;
}
}
/**
* 內(nèi)部認(rèn)證
* @param commandApdu
* @return
*/
private byte[] internalAuth(byte[] commandApdu) {
if (commandApdu.length == 13 && Arrays.equals(ByteUtils.getSubArray(commandApdu,0,5),ByteUtils.toByteArray(ApduCommands.INTERNAL_AUTH))){
byte[] randArray = ByteUtils.getSubArray(commandApdu, 5, 8);
logAppend(TAG + " : 88隨機(jī)數(shù) <--- " + ByteUtils.toHexString(randArray));
byte[] desEncrypt = Aes128EcbUtils.DESede(randArray,
ByteUtils.toByteArray(MmkvUtils.decodeString("INTERNAL_AUTH_KEY")),
Cipher.ENCRYPT_MODE);
logAppend(TAG + SEND + ByteUtils.toHexString(ByteUtils.concatenate(desEncrypt,ApduCommands.SW_9000)));
return ByteUtils.concatenate(desEncrypt,ApduCommands.SW_9000);
}
else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6A82));
return ApduCommands.SW_6A82;
}
}
/**
* 擦除目錄文件
* @param commandApdu
* @return
*/
private byte[] eraseDF(byte[] commandApdu) {
if (commandApdu.length == 5 && Arrays.equals(HexDump.getSubArray(commandApdu,0,5),HexDump.hexStringToByteArray(ApduCommands.ERASE_DF))){
clearCardData();
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6982));
return ApduCommands.SW_6982;
}
}
/**
* 創(chuàng)建文件
* @param commandApdu
* @return
*/
private byte[] createFile(byte[] commandApdu) {
if (commandApdu.length == 17 && Arrays.equals(commandApdu,ByteUtils.toByteArray(ApduCommands.CREATE_EC01_FILE))){
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (commandApdu.length == 12 && Arrays.equals(commandApdu,ByteUtils.toByteArray(ApduCommands.CREATE_SECRET_KEY_FILE))){
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (commandApdu.length == 12 && Arrays.equals(commandApdu,ByteUtils.toByteArray(ApduCommands.CREATE_FILE_01))){
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (commandApdu.length == 12 && Arrays.equals(commandApdu,ByteUtils.toByteArray(ApduCommands.CREATE_FILE_02))){
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6982));
return ApduCommands.SW_6982;
}
}
/**
* 寫KEY
* @param commandApdu
* @return
*/
private byte[] writeKey(byte[] commandApdu) {
if (commandApdu.length == 26 && Arrays.equals(ByteUtils.getSubArray(commandApdu,0,10),ByteUtils.toByteArray(ApduCommands.WRITE_EXTERNAL_AUTH_KEY))){
byte[] exAuthKeyBytes = ByteUtils.getSubArray(commandApdu,10,16);
MmkvUtils.encode("EXTERNAL_AUTH_KEY",ByteUtils.toHexString(exAuthKeyBytes));
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else if (commandApdu.length == 18 && Arrays.equals(ByteUtils.getSubArray(commandApdu,0,10),ByteUtils.toByteArray(ApduCommands.WRITE_INTERNAL_AUTH_KEY))){
byte[] inAuthKeyBytes = ByteUtils.getSubArray(commandApdu,10,8);
MmkvUtils.encode("INTERNAL_AUTH_KEY",ByteUtils.toHexString(inAuthKeyBytes));
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}
else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6982));
return ApduCommands.SW_6982;
}
}
/**
* 寫二進(jìn)制文件
* @param commandApdu
* @return
*/
private byte[] updateBinary(byte[] commandApdu) {
if (commandApdu.length == 9 && Arrays.equals(ByteUtils.getSubArray(commandApdu,0,5),ByteUtils.toByteArray(ApduCommands.WRITE_UID))){
byte[] uidBytes = ByteUtils.getSubArray(commandApdu,5,4);
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
MmkvUtils.encode("UID",ByteUtils.toHexString(uidBytes));
return ApduCommands.SW_9000;
}
else if (commandApdu.length == 13 && Arrays.equals(ByteUtils.getSubArray(commandApdu,0,5),ByteUtils.toByteArray(ApduCommands.WRITE_CUID))){
byte[] cuidBytes = ByteUtils.getSubArray(commandApdu,5,8);
MmkvUtils.encode("CUID",ByteUtils.toHexString(cuidBytes));
if (mHandler != null)
mHandler.sendEmptyMessage(2);
logAppend(TAG + SEND + ByteUtils.toHexString(ApduCommands.SW_9000));
return ApduCommands.SW_9000;
}else {
logAppend(TAG + SEND + HexDump.toHexString(ApduCommands.SW_6A81));
return ApduCommands.SW_6A81;
}
}
private void clearCardData() {
MmkvUtils.encode("UID","");
MmkvUtils.encode("CUID","");
MmkvUtils.encode("EXTERNAL_AUTH_KEY","");
MmkvUtils.encode("INTERNAL_AUTH_KEY","");
}
private void logAppend(String log){
if (mStrBuilder != null)
mStrBuilder.insert(0,log + "\r\n");
if (mHandler != null)
mHandler.sendEmptyMessage(1);
}
private void sendUpdateMessage(int pos) {
Message message = Message.obtain();
message.what = MESSAGE_UPDATE_PROGRESS;
message.arg1 = pos;
mHandler.sendMessage(message);
}
}
3:注冊(cè)CardEmulationService
<service
android:name="com.radio.www.service.CardEmulationService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE">
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
</intent-filter>
<meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="@xml/hceservice" />
</service>
4:配置hceservice.xml
hceservice.xml中配置AID,可以配置一個(gè)或多個(gè)AID反镇,AID是這個(gè)service標(biāo)識(shí),通過AID來找到對(duì)應(yīng)的service娘汞;這里也可以不配置AID歹茶,可以通過代碼動(dòng)態(tài)注冊(cè)AID。
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/hce_service_descr"
android:requireDeviceUnlock="false">
<aid-group android:description="@string/hce_aid_descr"
android:category="other">
<!-- TODO: change ID to F...-->
<!-- see: https://stackoverflow.com/questions/27533193/android-hce-are-there-rules-for-aid-->
<!-- 以“A”開頭的AID:國際注冊(cè)的AID-->
<!-- 以“D”開頭的AID:國家注冊(cè)的AID-->
<!-- 以“F”開頭的AIDs:專有AIDs(無需注冊(cè))-->
<aid-filter android:name ="444639395f3030303030303030303030"/>
</aid-group>
</host-apdu-service>
5:動(dòng)態(tài)注冊(cè)AID
使用CardEmulation類實(shí)現(xiàn)代碼動(dòng)態(tài)注冊(cè)AID:
package com.roy.www.nfc_configcard.ui.activity.
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.nfc.NfcAdapter;
import android.nfc.cardemulation.CardEmulation;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import com.roy.www.nfc_configcard.R;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by Roy.lee
* On 2022/6/27
* Email: 631934797@qq.com
* Description:
*/
public class MainActivity extends AppCompatActivity {
private CardEmulation mCardEmulation;
private ComponentName mService;
private static final List<String> AIDS = new ArrayList<>();
static {
AIDS.add("444639395f3030303030303030303030");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NfcAdapter mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
mCardEmulation = CardEmulation.getInstance(mNfcAdapter);
mService = new ComponentName(this, CardEmulationService.class);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onResume() {
super.onResume();
mCardEmulation.setPreferredService(this, mService);
mCardEmulation.registerAidsForService(mService, "other", AIDS);
startService(CardEmulationService.newHCEServiceIntent(MainActivity.this, mHandler, mStrBuf));
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onPause() {
super.onPause();
Log.d("CardEmulation", "removeAidsForService");
mCardEmulation.removeAidsForService(mService, "other");
mCardEmulation.unsetPreferredService(this);
}
}