如何通过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
相关产品推荐
相关产品推荐

