Android使用NFC實(shí)現(xiàn)模擬卡

一得运、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);
    }


}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載你弦,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者惊豺。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市禽作,隨后出現(xiàn)的幾起案子尸昧,更是在濱河造成了極大的恐慌,老刑警劉巖旷偿,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烹俗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡萍程,警方通過查閱死者的電腦和手機(jī)幢妄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茫负,“玉大人蕉鸳,你說我怎么就攤上這事∪谭ǎ” “怎么了置吓?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缔赠。 經(jīng)常有香客問我衍锚,道長,這世上最難降的妖魔是什么嗤堰? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任戴质,我火速辦了婚禮度宦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘告匠。我一直安慰自己戈抄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布后专。 她就那樣靜靜地躺著划鸽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戚哎。 梳的紋絲不亂的頭發(fā)上裸诽,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音型凳,去河邊找鬼丈冬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甘畅,可吹牛的內(nèi)容都是我干的埂蕊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼疏唾,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蓄氧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起槐脏,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤匀们,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后准给,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡重抖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年露氮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钟沛。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畔规,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恨统,到底是詐尸還是另有隱情叁扫,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布畜埋,位于F島的核電站莫绣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悠鞍。R本人自食惡果不足惜对室,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掩宜,春花似錦蔫骂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至檐迟,卻和暖如春补胚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锅减。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國打工糖儡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怔匣。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓握联,卻偏偏與公主長得像,于是被迫代替她去往敵國和親每瞒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子金闽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容