Android 11 开发者预览版现已推出;快来测试并分享您的反馈吧

基于主机的卡模拟概览

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

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

使用安全元件进行卡模拟

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

图 1. 使用安全元件进行卡模拟。

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

基于主机的卡模拟

使用“基于主机的卡模拟”这一方式来模拟 NFC 卡时,系统会将数据发送到 Android 应用直接在其上运行的主机 CPU,而不是将 NFC 协议框架发送到安全元件。图 2 演示了基于主机的卡模拟方式的工作原理。

图 2. 不涉及安全元件的 NFC 卡模拟。

支持的 NFC 卡和协议

图 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)的模拟卡,并处理应用协议数据单元 (APDU)(如 ISO/IEC 7816-4 规范中所定义)。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 通常为人熟知且已公开注册(例如,支付网络 Visa 和 MasterCard 等的 AID)。

如果您想为自己的应用部署新的读取器基础设施,则需要注册自己的 AID。AID 的注册流程在 ISO/IEC 7816-5 规范中得到了定义。如果您要部署 Android 版 HCE 应用,Google 建议您根据 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 支持两种类别:CATEGORY_PAYMENT(涵盖符合行业标准的付款应用)和 CATEGORY_OTHER(适用于所有其他 HCE 应用)。

注意:在任何给定时间,系统中只能启用 CATEGORY_PAYMENT 类别中的一个 AID 群组。通常情况下,这将是可了解主要的信用卡付款协议并适用于任何商家的应用。

对于只能在一个商家使用的封闭式付款应用(例如储值卡),您应使用 CATEGORY_OTHER此类别中的 AID 群组可以始终处于活跃状态,并且可以在 AID 选择期间由 NFC 读取器给予优先级(如有必要)。

实现 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 声明了需要替换和实现的两个抽象方法。

只要 NFC 读取器向您的服务发送应用协议数据单元 (APDU),系统就会调用 processCommandApdu()。APDU 也在 ISO/IEC 7816-4 规范中定义。APDU 是在 NFC 读取器和您的 HCE 服务之间进行交换的应用级数据包。该应用级协议为半双工:NFC 读取器会向您发送命令 APDU,反之它会等待您发送响应 APDU。

注意:ISO/IEC 7816-4 规范也定义了多逻辑通道的概念,您可以在单逻辑通道上具有多个并行的 APDU。不过,Android 的 HCE 实现仅支持单逻辑通道,因此只有一个单线程的 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 转发到您的服务,直到发生下列任一情况:

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

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

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

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

服务清单声明和 AID 注册

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

首先,要告知平台它是实现 HostApduService 接口的 HCE 服务,您的服务声明必须包含针对 SERVICE_INTERFACE 操作的 Intent 过滤器。

此外,要告知平台此服务请求了哪些 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> 属性,其中包含可在界面中显示的简单易懂的服务说明。requireDeviceUnlock 属性可用于指定必须先解锁设备,然后才能调用此服务来处理 APDU。

<host-apdu-service> 必须包含一个或多个 <aid-group> 标记。每个 <aid-group> 标记都必须符合以下条件:

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

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

AID 冲突解决

单个设备上可以安装多个 HostApduService 组件,同一个 AID 可由多个服务注册。Android 平台会根据 AID 所属的类别来解决 AID 冲突。每个类别可能使用不同的冲突解决政策。

例如,对于某些类别(如付款),用户可以在 Android 设置界面中选择默认服务。对于其他类别,具体政策可能是始终询问用户在发生冲突时调用哪项服务。要查询特定类别的冲突解决政策,请参阅 getSelectionModeForCategory()

检查您的服务是否为默认服务

应用可以使用 isDefaultServiceForCategory(ComponentName, String) API 检查其 HCE 服务是否为某个特定类别的默认服务。

如果您的服务不是默认服务,则可以请求将其设置为默认服务。请参阅 ACTION_CHANGE_DEFAULT

付款应用

Android 会将声明了具有“付款”类别的 AID 群组的 HCE 服务视为付款应用。Android 4.4 版本包含名为“触碰付款”的顶级设置菜单,其中枚举了所有此类付款应用。在此设置菜单中,用户可以选择在触碰付款终端时调用的默认付款应用。

付款应用必需的资源

为了提供更具视觉吸引力的用户体验,HCE 付款应用必须为其服务提供附加资源,也就是所谓的服务横幅。

此资源的大小应为 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 实现会在设备的屏幕处于关闭状态时完全关闭 NFC 控制器和应用处理器。因此,当屏幕处于关闭状态时,HCE 服务将不起作用。

HCE 服务可以在锁定屏幕上运行,但由 HCE 服务的 <host-apdu-service> 标记中的 android:requireDeviceUnlock 属性控制。默认情况下,设备不需要解锁,即使设备处于锁定状态,系统也会调用您的服务。

如果您针对 HCE 服务将 android:requireDeviceUnlock 属性设为“true”,则 Android 会在您触碰 NFC 读取器(该读取器选择了一个会解析到您的服务的 AID)时提示用户解锁设备。设备解锁后,Android 会显示一个对话框,提示用户点按以完成交易。这是必要操作,因为用户可能已将设备从 NFC 读取器上移开来解锁设备。

与安全元件卡共存

此部分适用于部署了依靠安全元件进行卡模拟的应用的开发者。Android 的 HCE 实现能够与其他实现卡模拟的方法并行工作,包括使用安全元件。

注意:Android 不提供用于直接与安全元件本身通信的 API。

这种共存基于一种称为“AID 路由”的原则:NFC 控制器保留一个包含路由规则(无限)列表的路由表。每个路由规则都包含一个 AID 和一个目的地。目的地可以是主机 CPU(Android 应用的运行位置),也可以是连接的安全元件。

当 NFC 读取器发送“SELECT AID”APDU 时,NFC 控制器会对其进行解析,并检查其 AID 是否与其路由表中的任何 AID 相匹配。如果匹配,系统会将该 APDU 及其后的所有 APDU 发送到与该 AID 相关联的目的地,直到收到另一个“SELECT AID”APDU 或 NFC 链接断开。

注意:虽然 ISO/IEC 7816-4 也定义了“部分匹配”的概念,但 Android HCE 设备目前不支持此功能。

图 4 展示了这种架构。

图 4. 同时运行安全元件和主机卡模拟的 Android 设备。

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

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

我们已经介绍了如何为 HCE 服务声明 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 服务无法对其进行配置。不过,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 设备。