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

一、NFC概述

NFC(Near Field Communication)也叫近距離無線通信,是一項(xiàng)無線技術(shù)。 NFC由非接觸式射頻識別(RFID)及互聯(lián)互通技術(shù)整合演變而來,在單一芯片上結(jié)合感應(yīng)式讀卡器、感應(yīng)式卡片和點(diǎn)對點(diǎn)的功能,利用移動(dòng)終端能在短距離內(nèi)與兼容設(shè)備進(jìn)行識別和數(shù)據(jù)交換。

NFC具有距離近、帶寬高、能耗低等特點(diǎn)。適用于一些敏感信息或個(gè)人數(shù)據(jù)的傳輸?shù)?,在安全性上具有?yōu)勢,NFC與現(xiàn)有非接觸智能卡技術(shù)兼容,已經(jīng)成為得到越來越多主要廠商支持的正式標(biāo)準(zhǔn)。利用NFC功能,可以實(shí)現(xiàn)消費(fèi)、門禁等多種應(yīng)用,代替卡包里的多種卡片,如移動(dòng)支付、電子票務(wù)、門禁、移動(dòng)身份識別、防偽等應(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ù),而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è)備對設(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.接觸、連接。將兩臺支持NFC的設(shè)備鏈接,即可進(jìn)行點(diǎn)對點(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)對點(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ā)送某種磁場,而這個(gè)磁場會(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ā)過來的信號,在通過一系列復(fù)雜的驗(yàn)證后,將IC卡的相應(yīng)信息傳入NFC射頻器,最后這些IC卡數(shù)據(jù)會(huì)傳入NFC射頻器連接的電腦,并進(jìn)行相應(yīng)的處理(如電子轉(zhuǎn)帳、開門等操作)。

3、點(diǎn)對點(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)對點(diǎn)模式的典型應(yīng)用是兩部支持NFC的手機(jī)或平板電腦實(shí)現(xiàn)數(shù)據(jù)的點(diǎn)對點(diǎn)傳輸,例如,交換圖片或同步設(shè)備聯(lián)系人。因此,通過NFC,多個(gè)設(shè)備如數(shù)字相機(jī),計(jì)算機(jī),手機(jī)之間,都可以快速連接,并交換資料或者服務(wù)。

下面對比一下NFC、藍(lán)牙和紅外之間的差異:
對比項(xiàng) NFC 藍(lán)牙 紅外 網(wǎng)絡(luò)類型 點(diǎn)對點(diǎn) 單點(diǎn)對多點(diǎn) 點(diǎn)對點(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)配置和固件升級功能;
1:通過手機(jī)NFC模擬配置卡功能來配置讀頭相關(guān)參數(shù);
2:通過手機(jī)NFC模擬電子工牌,實(shí)現(xiàn)刷手機(jī)開門功能;
3:通過手機(jī)NFC模擬卡片升級,升級讀頭固件等功能;
NFC具體細(xì)節(jié)可參閱博文這里不做過多贅述,直接上代碼:

1:NFC權(quán)限
 <uses-permission android:name="android.permission.NFC" />

    <!--API 9 設(shè)備可以使用近場通信(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)用程序級數(shù)據(jù)包。該應(yīng)用程序級協(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 + " : 升級成功");
            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:注冊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)識,通過AID來找到對應(yīng)的service;這里也可以不配置AID,可以通過代碼動(dòng)態(tài)注冊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:國際注冊的AID-->
        <!--        以“D”開頭的AID:國家注冊的AID-->
        <!--        以“F”開頭的AIDs:專有AIDs(無需注冊)-->
        <aid-filter android:name ="444639395f3030303030303030303030"/>
    </aid-group>
</host-apdu-service>

5:動(dòng)態(tài)注冊AID

使用CardEmulation類實(shí)現(xiàn)代碼動(dòng)態(tài)注冊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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容