基于主机的卡模拟概览

许多提供 NFC 功能的 Android 设备已经支持 NFC 卡模拟。在大多数情况下,卡由设备中的一个单独的芯片(称为安全元件)进行模拟。无线运营商提供的很多 SIM 卡也包含安全元件。

Android 4.4 及更高版本提供了另一种不涉及安全元件的卡模拟方法,名为“基于主机的卡模拟”。这样一来,任何 Android 应用都可以模拟卡并直接与 NFC 读取器通信。本主题介绍了基于主机的卡模拟 (HCE) 在 Android 上的工作原理以及您可如何开发使用此技术模拟 NFC 的应用。

使用安全元件进行卡模拟

使用安全元件提供 NFC 卡模拟时,要模拟的卡会通过 Android 应用配置到设备上的安全元件中。然后,当用户将设备靠近 NFC 终端时,设备中的 NFC 控制器会将来自读取器的所有数据直接路由到安全元件。图 1 说明了这一概念:

NFC 读取器通过 NFC 控制器从安全元件检索信息的示意图
图 1. 使用安全元件进行卡模拟。

安全元件本身与 NFC 终端进行通信,交易中不涉及任何 Android 应用。交易完成后,Android 应用可以直接查询安全元件以获取交易状态并通知用户。

基于主机的卡模拟

使用基于主机的卡模拟来模拟 NFC 卡时,数据会直接路由到主机 CPU,而不是安全元件。图 2 演示了基于主机的卡模拟方式的工作原理:

示意图:NFC 读取器通过 NFC 控制器从 CPU 检索信息
图 2. 不涉及安全元件的 NFC 卡模拟。

支持的 NFC 卡和协议

显示 HCE 协议栈的示意图
图 3. Android 的 HCE 协议栈。

NFC 标准支持很多不同的协议,并且您可以模拟不同类型的卡。

Android 4.4 及更高版本支持目前市面上常见的多种协议。很多现有的感应式卡就采用这些协议,例如感应式支付卡。目前市面上的很多 NFC 读取器也支持这些协议,其中包括本身就可充当读取器的 Android NFC 设备(请参阅 IsoDep 类)。这样,您就可以仅使用 Android 设备围绕 HCE 构建和部署端到端 NFC 解决方案。

具体来说,Android 4.4 及更高版本支持模拟基于 NFC-Forum ISO-DEP 规范(基于 ISO/IEC 14443-4)的卡,并处理 ISO/IEC 7816-4 规范中定义的应用协议数据单元 (APDU)。Android 要求仅使用 Nfc-A (ISO/IEC 14443-3 Type A) 技术模拟 ISO-DEP。也可以支持 Nfc-B (ISO/IEC 14443-4 Type B) 技术。图 3 说明了所有这些规范的分层。

HCE 服务

Android 中的 HCE 架构基于 Android Service 组件(称为 HCE 服务)。服务的一项关键优势是可以在后台运行,而不显示任何界面。这非常适合很多 HCE 应用(例如会员卡或公交卡),用户不需要启动应用即可使用。设备触碰到 NFC 读取器时,系统会启动正确的服务(如果服务尚未运行),并在后台执行交易。当然,您也可以根据需要从自己的服务启动额外的界面(例如用户通知)。

服务选择

当用户手拿设备触碰 NFC 读取器时,Android 系统需要知道 NFC 读取器想与哪项 HCE 服务通信。ISO/IEC 7816-4 规范定义了一种以应用 ID (AID) 为核心的应用选择方式。一个 AID 最多可包含 16 个字节。如果您要为现有的 NFC 读取器基础架构模拟卡,则这些读取器查找的 AID 通常是众所周知的公开注册 ID(例如,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 服务,而另一些发到到另一项 HCE 服务这种介于两者之间的状态。

AID 群组和类别

您可以将每个 AID 群组与一个类别相关联。这样,Android 便可按类别对 HCE 服务进行分组,从而允许用户在类别级(而不是 AID 级)设置默认值。请避免在应用任何面向用户的部分提及 AID,因为它们对普通用户而言没有任何意义。

Android 4.4 及更高版本支持以下两类:

实现 HCE 服务

如需使用基于主机的卡模拟来模拟 NFC 卡,您需要创建一个用于处理 NFC 交易的 Service 组件。

检查 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。然后,您可以在另一个线程上执行必要的操作,并在完成后使用 HostApduService 类中定义的 sendResponseApdu() 方法发送响应。

Android 会继续将读取器的新 APDU 转发到您的服务,直到发生下列任一情况:

  • NFC 读取器发送另一个 SELECT AID APDU,操作系统会将其解析为其他服务。
  • NFC 读取器和设备之间的 NFC 链接断开。

在这两种情况下,系统会调用您类的 onDeactivated() 实现,其参数指示发生了以上哪种情况。

如果您正在使用现有的读取器基础架构,则必须实现读取器希望您的 HCE 服务使用的现有应用级协议。

如果要部署也由您控制的新读取器基础架构,您可以定义自己的协议和 APDU 序列。尝试限制 APDU 的数量以及要交换的数据大小:这样可以确保您的用户只需将设备靠近 NFC 读取器一小段时间即可。合理的数据大小上限约为 1 KB,通常可在 300 毫秒内交换。

服务清单声明和 AID 注册

您必须像往常一样在清单中声明您的服务,但您还必须在服务声明中添加一些其他部分:

  1. 如需告知平台它是实现 HostApduService 接口的 HCE 服务,请向服务声明中添加针对 SERVICE_INTERFACE 操作的 intent 过滤器。

  2. 如需告知平台此服务请求了哪些 AID 群组,请在服务声明中添加 SERVICE_META_DATA <meta-data> 标记,并指向提供 HCE 服务附加信息的 XML 资源。

  3. android:exported 属性设为 true,并在服务声明中要求用户提供 android.permission.BIND_NFC_SERVICE 权限。前者可确保外部应用可以绑定该服务。然后,后者强制要求只有拥有 android.permission.BIND_NFC_SERVICE 权限的外部应用才能绑定到您的服务。由于 android.permission.BIND_NFC_SERVICE 是系统权限,因此这样可以有效地强制要求只有 Android OS 才能绑定到您的服务。

以下是 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> 属性,该属性包含可在应用界面中显示的简单易懂的服务说明。您可以使用 requireDeviceUnlock 属性指定在调用此服务来处理 APDU 之前设备已解锁。

<host-apdu-service> 必须包含一个或多个 <aid-group> 标记。每个 <aid-group> 标记都必须执行以下操作:

  • 包含 android:description 属性,其中包含适合在界面中显示的简单易懂的 AID 群组说明。
  • 将其 android:category 属性设置为指示 AID 群组所属的类别,例如,由 CATEGORY_PAYMENTCATEGORY_OTHER 定义的字符串常量。
  • 包含一个或多个 <aid-filter> 标记,其中每个标记都包含一个 AID。以十六进制格式指定 AID,并确保其包含的字符数为偶数。

您的应用还需要拥有 NFC 权限才能注册为 HCE 服务。

AID 冲突解决

一台设备上可以安装多个 HostApduService 组件,并且同一个 AID 可由多项服务注册。Android 会按照以下步骤确定要调用哪项服务:

  1. 如果用户选择的默认钱包应用已注册 AID,系统会调用该应用。
  2. 如果默认的钱包应用未注册 AID,则会调用已注册 AID 的服务。
  3. 如果有多个服务注册了 AID,Android 会询问用户要调用哪项服务。

检查您的应用是否为默认钱包应用

通过将 RoleManager.ROLE_WALLET 传递给 RoleManager.isRoleHeld(),应用可以检查自己是否是默认钱包应用。

如果您的应用不是默认应用,您可以通过将 RoleManager.ROLE_WALLET 传递给 RoleManager.createRequestRoleIntent() 来请求默认钱包角色。

钱包应用

Android 会将声明了包含付款类别的 AID 群组的 HCE 服务视为钱包应用。Android 15 及更高版本包含默认钱包应用角色,用户可以通过依次选择设置 > 应用 > 默认应用来选择该角色。此属性用于定义在用户点按付款终端时要调用的默认钱包应用。

钱包应用必需的资源

为了提供更具视觉吸引力的用户体验,HCE 钱包应用必须提供服务横幅。

Android 13 及更高版本

为了更好地适应“设置”界面中的默认付款方式选择列表,请将横幅要求调整为方形图标。理想情况下,图标设计应与应用启动器图标设计完全相同。此调整可使界面更加一致且看起来更简洁。

Android 12 及更低版本

将服务横幅的大小设置为 260x96 dp,然后通过向指向可绘制资源的 <host-apdu-service> 标记添加 android:apduServiceBanner 属性,在元数据 XML 文件中设置服务横幅的大小。以下是一个示例:

<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 注册轮询循环过滤器:

当轮询循环过滤器与非标准轮询帧匹配时,NFC 堆栈会通过调用其 processPollingFrames() 方法将这些轮询帧路由到相应的 HostApduService。这样一来,服务便可以采取任何必要的步骤来确保用户已准备好进行交易并打算这样做,例如对用户进行身份验证。如果 NFC 读卡器在其轮询循环中仅使用标准帧,则 NFC 堆栈会将这些轮询帧路由到首选前台服务(如果该服务位于前台),否则会路由到默认的钱包角色持有者。

轮询帧通知还包含供应商专有的场强测量值,您可以通过调用 getVendorSpecificGain() 检索该值。供应商可以使用自己的量表提供测量结果,前提是该量表可容纳在单个字节内。

响应轮询循环并退出观察模式

当服务准备好进行事务时,可以通过将 false 传递给 setObserveModeEnabled() 退出观察模式。然后,NFC 堆栈将允许交易继续进行。

HostApduService 组件可以通过在清单中将 shouldDefaultToObserveMode 设置为 true 或调用 CardEmulation.setShouldDefaultToObserveModeForService(),指明每当它们是首选付款服务时都应启用观察模式。

HostApduServiceOffHostApduService 组件还可以指明,与收到的轮询循环帧匹配的轮询循环过滤器应自动停用观察模式,并允许事务继续进行,方法是在清单中的 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 读取器与安全元件和 CPU 通信
图 4. 同时使用安全元件和主机卡模拟运行的 Android。

NFC 控制器通常还包含 APDU 的默认路由。如果未在路由表中找到某个 AID,则系统会使用默认路由。虽然此设置可能会因设备而异,但 Android 设备必须确保应用注册的 AID 正确路由到主机。

实现 HCE 服务或使用安全元件的 Android 应用无需担心路由表配置问题,这由 Android 自动处理。Android 只需知道 HCE 服务可以处理哪些 AID,以及安全元件可以处理哪些 AID。根据安装的服务以及用户已配置为首选项的服务,系统会自动配置路由表。

以下部分介绍了如何为使用安全元件进行卡模拟的应用声明 AID。

安全元件 AID 注册

使用安全元件进行卡模拟的应用可在其清单中声明脱离主机的服务。此类服务的声明几乎与 HCE 服务的声明完全相同。例外情况如下:

  • intent 过滤器中使用的操作必须设置为 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(Request for Answer To Select)命令。RATS 响应(即 ATS)由 NFC 控制器生成;HCE 服务无法对 ATS 进行配置。不过,HCE 实现必须满足 NFC Forum 在 ATS 响应方面的要求,因此 NFC 读取器可以依赖根据 NFC Forum 要求针对任何 HCE 设备设置的这些参数。

以下部分详细介绍了 NFC 控制器在 HCE 设备上提供的 ATS 响应的各个字节:

  • TL:ATS 响应的长度。长度不得大于 20 个字节。
  • T0:必须在所有 HCE 设备上设置位 5、位 6 和位 7,表示 ATS 响应中包含 TA(1)、TB(1) 和 TC(1)。位 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 设备。