You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过NFC从借记卡/信用卡中获取AID?

无法通过NFC读取借记卡/信用卡的AID

我需要通过NFC扫描借记卡/信用卡获取AID,谷歌应用商店里有不少同类应用能轻松实现,但我一直没成功。选择获取AID而非卡号、有效期等信息,是因为客户收到了法律通知,要求用AID替代卡片信息。

已尝试操作

  • 在Manifest文件中添加服务引用
  • 创建apduservice.xml并在Manifest中关联

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.NFC" />
    <uses-feature
        android:name="android.hardware.nfc.hce"
        android:required="true" />
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.NFC"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <service
            android:name=".NdefHostApduService"
            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/apduservice" />
        </service>
    </application>
</manifest>

apduservice.xml

<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/servicedesc"
    android:requireDeviceUnlock="false">
    <aid-group android:description="@string/aiddescription"
        android:category="other">
        <aid-filter android:name="A0000002471001"/>
        <aid-filter android:name="F00000000A0101"/>
        <aid-filter android:name="F0394148148100"/>
        <aid-filter android:name="D2760000850101"/>
        <aid-filter android:name="A0000000041010"/>
        <aid-filter android:name="A0000000031010"/> <!-- Visa -->
        <aid-filter android:name="A0000000041010"/> <!-- Mastercard -->
        <aid-filter android:name="A00000002501"/>   <!-- American Express -->
        <aid-filter android:name="A0000000042203"/> <!-- Discover -->
        <aid-filter android:name="A0000000651010"/> <!-- JCB -->
        <aid-filter android:name="A0000003330101"/> <!-- UnionPay -->
    </aid-group>
</host-apdu-service>

NdefHostApduService.java(processCommandApdu始终未触发)

package com.example.nfc;
import android.content.Context;
import android.content.Intent;
import android.nfc.NdefRecord;
import android.nfc.cardemulation.HostApduService;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.widget.Toast;
import java.math.BigInteger;
import java.nio.charset.Charset;

public class NdefHostApduService extends HostApduService {
    public NdefHostApduService(){
    }
    private static final String TAG = "NdefHostApduService";
    String STATUS_SUCCESS = "9000";
    String STATUS_FAILED = "6F00";
    String CLA_NOT_SUPPORTED = "6E00";
    String INS_NOT_SUPPORTED = "6D00";
    String AID = "A0000002471001";
    String SELECT_INS = "A4";
    String DEFAULT_CLA = "00";
    int MIN_APDU_LENGTH = 12;
    private static final byte[] NDEF_ID = {
            (byte)0xE1,
            (byte)0x04
    };
    private NdefRecord NDEF_URI = new NdefRecord(
            NdefRecord.TNF_WELL_KNOWN,
            NdefRecord.RTD_TEXT,
            NDEF_ID,
            "Hello world!".getBytes(Charset.forName("UTF-8"))
    );
    private byte[] NDEF_URI_BYTES = NDEF_URI.toByteArray();
    private byte[] NDEF_URI_LEN = BigInteger.valueOf(NDEF_URI_BYTES.length).toByteArray();

    @Override
    public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
        String commandApduHex = toHexString(commandApdu);
        if (commandApduHex.startsWith("00A404")) {
            String aid = commandApduHex.substring(6);
            Log.d(TAG, "Selected AID: " + aid);
        }
        return new byte[0];
    }

    @Override
    public void onDeactivated(int reason)
    {
        Log.d(TAG, "Deactivated: " + reason);
    }

    private static String toHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }
}

问题核心原因

你当前用的是HCE(主机卡模拟)服务,这个功能是让Android设备模拟成一张NFC卡,接收外部设备的读取请求。但你需要的是读卡器模式——让设备读取实体银行卡的信息,所以processCommandApdu方法永远不会触发,它只在设备作为模拟卡被读取时才会调用。

正确实现方案(读卡器模式读取AID)

1. 移除HCE相关配置

删除Manifest中的NdefHostApduService服务声明,以及apduservice.xml文件,这些是卡模拟专用配置,和读取实体卡无关。

2. 配置读卡器模式权限与Intent Filter

修改Manifest,专注监听实体NFC标签的发现事件,并指定支持银行卡使用的ISO 7816协议。

修改后的AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.NFC" />
    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.NFC"
        tools:targetApi="31">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

            <!-- 指定支持的NFC技术类型 -->
            <meta-data
                android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/nfc_tech_filter" />
        </activity>

    </application>
</manifest>

添加nfc_tech_filter.xml(res/xml目录下)

指定只监听支持ISO 7816协议的卡片:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
    </tech-list>
</resources>

3. 实现读卡器逻辑(MainActivity)

通过NfcAdapter监听卡片,建立ISO连接,发送SELECT APDU命令获取AID:

package com.example.nfc;

import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "NFCReader";
    private TextView aidTextView;
    private NfcAdapter nfcAdapter;

    // 主流银行卡AID列表
    private static final String[] AID_LIST = {
            "A0000000031010", // Visa
            "A0000000041010", // Mastercard
            "A00000002501",   // American Express
            "A0000000042203", // Discover
            "A0000000651010", // JCB
            "A0000003330101"  // UnionPay
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        aidTextView = findViewById(R.id.aid_text);
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);

        if (nfcAdapter == null) {
            aidTextView.setText("设备不支持NFC");
            return;
        }

        // 处理启动时的NFC intent
        handleNfcIntent(getIntent());
    }

    @Override
    protected void onNewIntent(android.content.Intent intent) {
        super.onNewIntent(intent);
        handleNfcIntent(intent);
    }

    private void handleNfcIntent(android.content.Intent intent) {
        String action = intent.getAction();
        if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            if (tag != null) {
                readCardAID(tag);
            }
        }
    }

    private void readCardAID(Tag tag) {
        IsoDep isoDep = IsoDep.get(tag);
        if (isoDep != null) {
            try {
                isoDep.connect();
                // 遍历AID列表,发送SELECT命令找到卡片支持的AID
                for (String aid : AID_LIST) {
                    byte[] selectApdu = buildSelectAIDApdu(aid);
                    byte[] response = isoDep.transceive(selectApdu);
                    // 检查响应状态字是否为0x9000(成功)
                    if (response != null && response.length >= 2) {
                        int status = ((response[response.length-2] & 0xFF) << 8) | (response[response.length-1] & 0xFF);
                        if (status == 0x9000) {
                            Log.d(TAG, "找到支持的AID: " + aid);
                            aidTextView.setText("卡片AID: " + aid);
                            break;
                        }
                    }
                }
                isoDep.close();
            } catch (IOException e) {
                Log.e(TAG, "读取卡片失败", e);
                aidTextView.setText("读取卡片失败: " + e.getMessage());
            }
        } else {
            aidTextView.setText("卡片不支持ISO 7816协议");
        }
    }

    // 构建SELECT AID的APDU命令
    private byte[] buildSelectAIDApdu(String aid) {
        byte[] aidBytes = hexStringToByteArray(aid);
        byte[] apdu = new byte[6 + aidBytes.length];
        apdu[0] = (byte) 0x00; // CLA
        apdu[1] = (byte) 0xA4; // INS: SELECT命令
        apdu[2] = (byte) 0x04; // P1: 按名称选择
        apdu[3] = (byte) 0x00; // P2: 默认参数
        apdu[4] = (byte) aidBytes.length; // Lc: AID长度
        System.arraycopy(aidBytes, 0, apdu, 5, aidBytes.length);
        apdu[5 + aidBytes.length] = (byte) 0x00; // Le: 期望返回数据长度
        return apdu;
    }

    // 十六进制字符串转字节数组
    private byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }
}

内容的提问来源于stack exchange,提问作者Muhammad Adeel Shoukat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 15:24:51