许多提供 NFC 功能的 Android 设备已支持 NFC 卡模拟。在大多数情况下,卡是由设备中的一个单独芯片(称为 *安全元件*)模拟的。无线运营商提供的许多 SIM 卡也包含安全元件。
Android 4.4 及更高版本提供了一种无需安全元件的额外卡模拟方法,称为 *基于主机的卡模拟*。这允许任何 Android 应用模拟卡并直接与 NFC 阅读器通信。本主题介绍基于主机卡模拟 (HCE) 在 Android 上的工作原理,以及如何开发使用此技术模拟 NFC 卡的应用。
使用安全元件进行卡模拟
当使用安全元件提供 NFC 卡模拟时,要模拟的卡将通过 Android 应用预配到设备上的安全元件中。然后,当用户将设备放在 NFC 终端上时,设备中的 NFC 控制器会将来自阅读器的所有数据直接路由到安全元件。图 1 说明了此概念
安全元件本身执行与 NFC 终端的通信,并且在交易中不涉及任何 Android 应用。交易完成后,Android 应用可以直接查询安全元件以获取交易状态并通知用户。
基于主机的卡模拟
当使用基于主机的卡模拟模拟 NFC 卡时,数据将直接路由到主机 CPU,而不是路由到安全元件。图 2 说明了基于主机的卡模拟的工作原理
支持的 NFC 卡和协议
NFC 标准提供了对许多不同协议的支持,并且您可以模拟不同类型的卡。
Android 4.4 及更高版本支持当今市场上常见的几种协议。许多现有的非接触式卡已基于这些协议,例如非接触式支付卡。这些协议也受当今市场上的许多 NFC 阅读器支持,包括充当阅读器的 Android NFC 设备本身(请参阅 IsoDep
类)。这使您能够仅使用 Android 设备围绕 HCE 构建和部署端到端 NFC 解决方案。
具体来说,Android 4.4 及更高版本支持模拟基于 NFC 论坛 ISO-DEP 规范(基于 ISO/IEC 14443-4)的卡片,并根据 ISO/IEC 7816-4 规范中定义的处理应用协议数据单元 (APDU)。Android 规定仅在 Nfc-A(ISO/IEC 14443-3 类型 A)技术之上模拟 ISO-DEP。对 Nfc-B(ISO/IEC 14443-4 类型 B)技术的支持是可选的。图 3 说明了所有这些规范的分层。
HCE 服务
Android 中的 HCE 架构基于 Android Service
组件(称为HCE 服务)。服务的关键优势之一是它可以在后台运行,无需任何用户界面。这非常适合许多 HCE 应用,例如会员卡或交通卡,用户无需启动应用即可使用。相反,将设备轻触 NFC 阅读器,如果服务尚未运行,则会启动正确的服务并在后台执行交易。当然,您可以在适当的时候从服务中启动其他 UI(例如用户通知)。
服务选择
当用户将设备轻触 NFC 阅读器时,Android 系统需要知道 NFC 阅读器想要与哪个 HCE 服务通信。ISO/IEC 7816-4 规范定义了一种选择应用的方法,其核心是应用 ID (AID)。AID 由最多 16 个字节组成。如果您正在为现有的 NFC 阅读器基础设施模拟卡片,则这些阅读器查找的 AID 通常是众所周知的并且公开注册的(例如,Visa 和 MasterCard 等支付网络的 AID)。
如果您想为自己的应用部署新的阅读器基础设施,则必须注册自己的 AID。AID 的注册过程在 ISO/IEC 7816-5 规范中定义。如果您正在为 Android 部署 HCE 应用,我们建议根据 7816-5 注册 AID,因为它可以避免与其他应用发生冲突。
AID 组
在某些情况下,HCE 服务可能需要注册多个 AID 并将其设置为所有 AID 的默认处理程序以实现特定应用。某些组中的 AID 转到其他服务不受支持。
一起保存的 AID 列表称为 AID 组。对于 AID 组中的所有 AID,Android 保证以下情况之一
- 组中的所有 AID 都路由到此 HCE 服务。
- 组中的任何 AID 都不路由到此 HCE 服务(例如,因为用户首选另一个服务,该服务也请求了您组中的一个或多个 AID)。
换句话说,不存在中间状态,其中组中的一些 AID 可以路由到一个 HCE 服务,而另一些 AID 可以路由到另一个服务。
AID 组和类别
您可以将每个 AID 组与一个类别关联。这允许 Android 按类别将 HCE 服务组合在一起,进而允许用户在类别级别而不是 AID 级别设置默认值。避免在应用的任何面向用户的部分中提及 AID,因为它们对普通用户没有任何意义。
Android 4.4 及更高版本支持两个类别
CATEGORY_PAYMENT
(涵盖行业标准支付应用)CATEGORY_OTHER
(适用于所有其他 HCE 应用)
实现 HCE 服务
要使用基于主机的卡模拟来模拟 NFC 卡,您需要创建一个 Service
组件来处理 NFC 交易。
检查 HCE 支持
您的应用可以通过检查 FEATURE_NFC_HOST_CARD_EMULATION
功能来检查设备是否支持 HCE。使用应用清单中的 <uses-feature>
标签声明您的应用使用 HCE 功能,以及它对于应用的功能是否必不可少。
服务实现
Android 4.4 及更高版本提供了一个方便的 Service
类,您可以将其用作实现 HCE 服务的基础:HostApduService
类。
第一步是扩展 HostApduService
,如下面的代码示例所示
Kotlin
class MyHostApduService : HostApduService() { override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray { ... } override fun onDeactivated(reason: Int) { ... } }
Java
public class MyHostApduService extends HostApduService { @Override public byte[] processCommandApdu(byte[] apdu, Bundle extras) { ... } @Override public void onDeactivated(int reason) { ... } }
HostApduService
声明了两个必须重写和实现的抽象方法。其中之一,processCommandApdu()
,在 NFC 阅读器将应用协议数据单元 (APDU) 发送到您的服务时被调用。APDU 在 ISO/IEC 7816-4 规范中定义。APDU 是在 NFC 阅读器和您的 HCE 服务之间交换的应用级数据包。该应用级协议是半双工的:NFC 阅读器会发送命令 APDU 给您,并等待您发送响应 APDU 以进行回复。
如前所述,Android 使用 AID 来确定阅读器想要与哪个 HCE 服务通信。通常,NFC 阅读器发送到您的设备的第一个 APDU 是 SELECT AID
APDU;此 APDU 包含阅读器想要与其通信的 AID。Android 从 APDU 中提取该 AID,将其解析为 HCE 服务,然后将该 APDU 转发到已解析的服务。
您可以通过从 processCommandApdu()
返回响应 APDU 的字节来发送响应 APDU。请注意,此方法是在应用的主线程上调用的,您不应该阻塞它。如果您不能立即计算并返回响应 APDU,则返回 null。然后,您可以在另一个线程上执行必要的工作,并使用 sendResponseApdu()
方法(在 HostApduService
类中定义)在完成后发送响应。
Android 会继续将来自阅读器的新 APDU 转发到您的服务,直到发生以下情况之一
- NFC 阅读器发送另一个
SELECT AID
APDU,操作系统将其解析为不同的服务。 - NFC 阅读器和您的设备之间的 NFC 链接断开。
在这两种情况下,都会调用您类的 onDeactivated()
实现,并带有一个指示发生了哪种情况的参数。
如果您正在使用现有的阅读器基础设施,则必须在您的 HCE 服务中实现阅读器期望的现有应用级协议。
如果您正在部署新的阅读器基础设施以及您控制的阅读器基础设施,则可以定义自己的协议和 APDU 序列。尝试限制 APDU 的数量和要交换的数据的大小:这确保您的用户只需将设备放在 NFC 阅读器上很短的时间。合理的上限约为 1 KB 的数据,通常可以在 300 毫秒内交换。
服务清单声明和 AID 注册
您必须像往常一样在清单中声明您的服务,但您还必须在服务声明中添加一些其他内容
要告诉平台它是一个实现
HostApduService
接口的 HCE 服务,请为您的服务声明添加SERVICE_INTERFACE
操作的意图过滤器。要告诉平台此服务请求哪些 AID 组,请在服务的声明中包含一个
SERVICE_META_DATA
<meta-data>
标签,指向包含有关 HCE 服务的其他信息的 XML 资源。将
android:exported
属性设置为true
,并在您的服务声明中要求android.permission.BIND_NFC_SERVICE
权限。前者确保服务可以被外部应用绑定。后者然后强制只有持有android.permission.BIND_NFC_SERVICE
权限的外部应用才能绑定到您的服务。由于android.permission.BIND_NFC_SERVICE
是系统权限,因此这实际上强制只有 Android 操作系统才能绑定到您的服务。
以下是一个 HostApduService
清单声明的示例
<service android:name=".MyHostApduService" 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>
此元数据标签指向 apduservice.xml
文件。以下是此类文件的示例,其中包含一个包含两个专有 AID 的 AID 组声明
<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="F0010203040506"/> <aid-filter android:name="F0394148148100"/> </aid-group> </host-apdu-service>
<host-apdu-service>
标签必须包含一个 <android:description>
属性,该属性包含服务的易于用户理解的描述,您可以在应用 UI 中显示该描述。您可以使用 requireDeviceUnlock
属性指定在调用此服务处理 APDU 之前设备已解锁。
<host-apdu-service>
必须包含一个或多个 <aid-group>
标签。每个 <aid-group>
标签都需要执行以下操作
- 包含一个
android:description
属性,该属性包含 AID 组的易于用户理解的描述,适合在 UI 中显示。 - 将其
android:category
属性设置为指示 AID 组所属的类别,例如CATEGORY_PAYMENT
或CATEGORY_OTHER
定义的字符串常量。 - 包含一个或多个
<aid-filter>
标签,每个标签都包含一个 AID。以十六进制格式指定 AID,并确保它包含偶数个字符。
您的应用还需要持有 NFC
权限才能注册为 HCE 服务。
AID 冲突解决
多个 HostApduService
组件可以安装在单个设备上,并且同一个 AID 可以由多个服务注册。Android 使用以下步骤确定要调用哪个服务
- 如果用户选择的默认钱包应用已注册 AID,则会调用该应用。
- 如果默认钱包应用未注册 AID,则会调用已注册 AID 的服务。
- 如果多个服务已注册 AID,则 Android 会询问用户要调用哪个服务。
检查您的应用是否为默认钱包应用
应用可以通过将 RoleManager.ROLE_WALLET
传递给 RoleManager.isRoleHeld()
来检查它们是否为默认钱包应用。
如果您的应用不是默认应用,您可以通过将RoleManager.ROLE_WALLET
传递给RoleManager.createRequestRoleIntent()
来请求默认钱包角色。
钱包应用
Android 将声明了包含支付类别的 AID 组的 HCE 服务视为钱包应用。Android 15 及更高版本包含一个默认钱包应用角色,用户可以通过导航到设置 > 应用 > 默认应用来选择。这定义了当轻触支付终端时要调用的默认钱包应用。
钱包应用所需的资源
为了提供更具视觉吸引力的用户体验,HCE 钱包应用需要提供服务横幅。
Android 13 及更高版本
为了更好地适应设置 UI 中的默认支付选择列表,将横幅要求调整为方形图标。理想情况下,它应该与应用启动器图标设计相同。此调整创造了更多的一致性和更简洁的外观。
Android 12 及更低版本
将服务横幅的大小设置为 260x96 dp,然后通过在元数据 XML 文件中添加android:apduServiceBanner
属性到<host-apdu-service>
标签来设置服务横幅的大小,该标签指向可绘制资源。以下是一个示例
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/servicedesc" android:requireDeviceUnlock="false" android:apduServiceBanner="@drawable/my_banner"> <aid-group android:description="@string/aiddescription" android:category="payment"> <aid-filter android:name="F0010203040506"/> <aid-filter android:name="F0394148148100"/> </aid-group> </host-apdu-service>
观察模式
Android 15 引入了观察模式功能。启用后,观察模式允许设备观察 NFC 轮询循环,并向相应的HostApduService
组件发送有关它们的通知,以便它们可以准备与给定的 NFC 终端交互。HostApduService
可以通过将true
传递给setObserveModeEnabled()
来将设备置于观察模式。这指示 NFC 堆栈不允许 NFC 事务,而是被动地观察轮询循环。
轮询循环过滤器
您可以使用以下任一方法为HostApduService
注册轮询循环过滤器
registerPollingLoopFilterForService()
用于必须完全匹配轮询帧的过滤器。registerPollingLoopPatternFilterForService()
用于对轮询帧进行正则表达式匹配的过滤器。
当轮询循环过滤器与非标准轮询帧匹配时,NFC 堆栈通过调用其processPollingFrames()
方法将这些轮询帧路由到相应的HostApduService
。这允许服务采取任何必要的步骤来确保用户已准备好进行交易并打算这样做——例如,对用户进行身份验证。如果 NFC 阅读器在其轮询循环中仅使用标准帧,则 NFC 堆栈将这些轮询帧路由到首选前台服务(如果该服务在前台)或默认钱包角色持有者(否则)。
轮询帧通知还包含一个供应商特定的场强测量值,您可以通过调用getVendorSpecificGain()
来检索它。供应商可以使用他们自己的比例提供测量值,只要它适合一个字节即可。
响应轮询循环并退出观察模式
当服务准备好进行交易时,它可以通过将false
传递给setObserveModeEnabled()
来退出观察模式。然后,NFC 堆栈将允许交易继续进行。
HostApduService
组件可以通过在清单中将shouldDefaultToObserveMode
设置为true
或通过调用CardEmulation.setShouldDefaultToObserveModeForService()
来指示在它们是首选支付服务时应启用观察模式。
HostApduService
和OffHostApduService
组件还可以指示匹配接收到的轮询循环帧的轮询循环过滤器应自动禁用观察模式并允许交易继续进行,方法是在清单中的PollingLoopFilter
声明中将autoTransact
设置为true
。
屏幕关闭和锁定屏幕行为
HCE 服务的行为因设备上运行的 Android 版本而异。
Android 12 及更高版本
在面向 Android 12(API 级别 31)及更高版本的应用中,您可以通过将requireDeviceScreenOn
设置为false
来在设备屏幕未开启的情况下启用 NFC 支付。
Android 10 及更高版本
运行 Android 10(API 级别 29)或更高版本的设备支持安全 NFC。当安全 NFC 处于开启状态时,设备屏幕关闭时所有卡模拟器(主机应用和非主机应用)都不可用。当安全 NFC 关闭时,设备屏幕关闭时非主机应用可用。您可以使用isSecureNfcSupported()
检查安全 NFC 支持。
在运行 Android 10 及更高版本的设备上,将android:requireDeviceUnlock
设置为true
的功能与运行 Android 9 及更低版本的设备相同,但仅在安全 NFC 关闭时适用。也就是说,如果安全 NFC 已开启,则无论android:requireDeviceUnlock
设置为何,HCE 服务都无法从锁定屏幕运行。
Android 9 及更低版本
在运行 Android 9(API 级别 28)及更低版本的设备上,当设备屏幕关闭时,NFC 控制器和应用处理器将完全关闭。因此,HCE 服务在屏幕关闭时无法工作。
此外,在 Android 9 及更低版本中,HCE 服务可以从锁定屏幕运行。但是,这由 HCE 服务的<host-apdu-service>
标签中的android:requireDeviceUnlock
属性控制。默认情况下,不需要设备解锁,即使设备已锁定,您的服务也会被调用。
如果您为 HCE 服务将android:requireDeviceUnlock
属性设置为true
,则在发生以下情况时,Android 会提示用户解锁设备
- 用户轻触 NFC 阅读器。
- NFC 阅读器选择解析为您的服务的 AID。
解锁后,Android 会显示一个对话框,提示用户再次轻触以完成交易。这是必要的,因为用户可能已将设备移开 NFC 阅读器以进行解锁。
与安全元件卡共存
本节适用于已部署依赖于安全元件进行卡模拟的应用的开发者。Android 的 HCE 实现旨在与其他卡模拟实现方法(包括使用安全元件)并行工作。
这种共存基于称为AID 路由的原则。NFC 控制器维护一个路由表,该路由表包含一个(有限的)路由规则列表。每个路由规则包含一个 AID 和一个目标。目标可以是主机 CPU(Android 应用正在运行的位置)或连接的安全元件。
当 NFC 阅读器发送带有SELECT AID
的 APDU 时,NFC 控制器会解析它并检查 AID 是否与其路由表中的任何 AID 匹配。如果匹配,则该 APDU 和其后的所有 APDU 都将发送到与 AID 关联的目标,直到收到另一个SELECT AID
APDU 或 NFC 链接断开。
图 4 说明了此架构
NFC 控制器通常还包含 APDUs 的默认路由。当在路由表中找不到 AID 时,将使用默认路由。虽然此设置可能因设备而异,但 Android 设备需要确保应用注册的 AID 正确路由到主机。
实现 HCE 服务或使用安全元件的 Android 应用无需担心配置路由表;这是由 Android 自动处理的。Android 只需要知道哪些 AID 可以由 HCE 服务处理,哪些 AID 可以由安全元件处理。路由表会根据安装的服务以及用户配置的首选服务自动配置。
下一节说明如何为使用安全元件进行卡模拟的应用声明 AID。
安全元件 AID 注册
使用安全元件进行卡模拟的应用可以在其清单中声明一个非主机服务。此类服务的声明与 HCE 服务的声明几乎相同。例外情况如下
- 意图过滤器中使用的操作必须设置为
SERVICE_INTERFACE
。 - 元数据名称属性必须设置为
SERVICE_META_DATA
。 元数据 XML 文件必须使用
<offhost-apdu-service>
根标签。<service android:name=".MyOffHostApduService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE"> <intent-filter> <action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/> </intent-filter> <meta-data android:name="android.nfc.cardemulation.off_host_apdu_service" android:resource="@xml/apduservice"/> </service>
以下是注册两个 AID 的相应apduservice.xml
文件的示例
<offhost-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/servicedesc"> <aid-group android:description="@string/subscription" android:category="other"> <aid-filter android:name="F0010203040506"/> <aid-filter android:name="F0394148148100"/> </aid-group> </offhost-apdu-service>
android:requireDeviceUnlock
属性不适用于非主机服务,因为主机 CPU 不参与交易,因此无法阻止安全元件在设备锁定时执行交易。
android:apduServiceBanner
属性是支付应用的非主机服务所必需的,以便将其选为默认支付应用。
非主机服务调用
Android 永远不会启动或绑定到声明为“非主机”的服务,因为实际事务由安全元件执行,而不是由 Android 服务执行。服务声明仅允许应用注册安全元件上存在的 AID。
HCE 和安全性
HCE 架构提供了一项核心安全功能:因为您的服务受BIND_NFC_SERVICE
系统权限的保护,所以只有操作系统才能绑定到您的服务并与之通信。这确保了您收到的任何 APDU 实际上是操作系统从 NFC 控制器接收到的 APDU,并且您发回的任何 APDU 只会发送到操作系统,而操作系统又会直接将 APDU 转发到 NFC 控制器。
最后一个剩下的问题是您的应用发送到 NFC 阅读器的来自哪里。这在 HCE 设计中被有意地解耦;它不关心数据来自哪里,它只确保数据安全地传输到 NFC 控制器并发送到 NFC 阅读器。
为了安全地存储和检索您要从 HCE 服务发送的数据,例如,您可以依赖 Android 应用沙箱,该沙箱将您的应用数据与其他应用隔离开。有关 Android 安全性的更多详细信息,请阅读安全提示。
协议参数和详细信息
本节内容适用于希望了解 HCE 设备在 NFC 协议的防冲突和激活阶段使用哪些协议参数的开发者。这有助于构建与 Android HCE 设备兼容的读卡器基础设施。
Nfc-A (ISO/IEC 14443 类型 A) 协议防冲突和激活
作为 Nfc-A 协议激活的一部分,会交换多个帧。
在交换的第一部分,HCE 设备会呈现其 UID;应假设 HCE 设备具有随机 UID。这意味着每次轻触时,呈现给读卡器的 UID 都是随机生成的 UID。因此,NFC 读卡器不应依赖 HCE 设备的 UID 作为身份验证或识别的形式。
随后,NFC 读卡器可以通过发送 SEL_REQ
命令来选择 HCE 设备。HCE 设备的 SEL_RES
响应至少设置了第 6 位 (0x20),指示设备支持 ISO-DEP。请注意,SEL_RES
中的其他位也可能被设置,例如指示支持 NFC-DEP (p2p) 协议。由于其他位可能被设置,因此希望与 HCE 设备交互的读卡器应仅显式检查第 6 位,并且**不要**将完整的 SEL_RES
与值 0x20 进行比较。
ISO-DEP 激活
激活 Nfc-A 协议后,NFC 读卡器会启动 ISO-DEP 协议激活。它会发送 RATS(选择响应请求)命令。NFC 控制器会生成 RATS 响应,即 ATS;ATS 无法由 HCE 服务配置。但是,HCE 实现必须满足 NFC 论坛对 ATS 响应的要求,因此 NFC 读卡器可以依靠这些参数根据 NFC 论坛对任何 HCE 设备的要求进行设置。
以下部分提供了有关 HCE 设备上的 NFC 控制器提供的 ATS 响应的各个字节的更多详细信息
- TL:ATS 响应的长度。不得指示长度大于 20 字节。
- T0:在所有 HCE 设备上必须设置第 5、6 和 7 位,指示 TA(1)、TB(1) 和 TC(1) 包含在 ATS 响应中。第 1 到第 4 位指示 FSCI,对最大帧大小进行编码。在 HCE 设备上,FSCI 的值必须在 0h 和 8h 之间。
- T(A)1:定义读卡器和模拟器之间的比特率,以及它们是否可以是非对称的。HCE 设备没有比特率要求或保证。
- T(B)1:第 1 到第 4 位指示启动帧保护时间整数 (SFGI)。在 HCE 设备上,SFGI 必须 <= 8h。第 5 到第 8 位指示帧等待时间整数 (FWI) 并对帧等待时间 (FWT) 进行编码。在 HCE 设备上,FWI 必须 <= 8h。
- T(C)1:第 5 位指示是否支持“高级协议功能”。HCE 设备可能支持也可能不支持“高级协议功能”。第 2 位指示是否支持 DID。HCE 设备可能支持也可能不支持 DID。第 1 位指示是否支持 NAD。HCE 设备不得支持 NAD,并将第 1 位设置为零。
- 历史字节:HCE 设备最多可以返回 15 个历史字节。希望与 HCE 服务交互的 NFC 读卡器不应对历史字节的内容或其存在做出任何假设。
请注意,许多 HCE 设备可能符合 EMVCo 联合的支付网络在其“非接触式通信协议”规范中指定的协议要求。特别是
- T0 中的 FSCI 必须在 2h 和 8h 之间。
- T(A)1 必须设置为 0x80,指示仅支持 106 kbit/s 比特率,并且不支持读卡器和模拟器之间的非对称比特率。
- T(B)1 中的 FWI 必须 <= 7h。
APDU 数据交换
如前所述,HCE 实现仅支持单个逻辑通道。尝试在不同的逻辑通道上选择应用程序在 HCE 设备上不起作用。