基于主机的卡模拟(HCE)概述

0x00 概述

许多提供NFC功能的基于Android的设备已经支持NFC卡模拟。在大多数情况下,该卡由设备中的单独芯片模拟,称为安全元件(Secure Element)。无线运营商提供的许多SIM卡还包含安全元件(Secure Element)。 Android 4.4引入了另一种卡模拟方法,它不涉及SE,称为基于主机的卡模拟。这允许任何Android应用程序模拟卡并直接与NFC读卡器通话。本文档描述了基于主机的卡仿真(HCE)如何在Android上工作,以及如何使用此技术开发模拟NFC卡的应用程序。

0x01 卡仿真与SE

当使用安全元件(Secure Element)提供NFC卡模拟时,将通过Android应用程序将要模拟的卡提供到设备上的安全元件(Secure Element)中。然后,当用户通过NFC终端握住设备时,设备中的NFC控制器将来自读卡器(NFC Reader)的所有数据直接路由到安全元件(Secure Element)。图1说明了这个概念。

图1.带有安全元件的NFC卡仿真

安全元件(Secure Element)本身执行与NFC终端的通信,并且完全不涉及Android应用。交易完成后,Android应用程序可以直接查询SE的交易状态并通知用户。

0x02 基于主机的卡模拟

当使用基于主机的卡仿真来仿真NFC卡时,数据将被路由到直接运行Android应用程序的主机CPU,而不是将NFC协议帧路由到SE。图2展示了基于主机的卡仿真如何工作。

图2.基于主机的卡仿真模式

0x03 支持的NFC卡和协议

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所示。

图3.Android的HCE协议栈

0x04 HCE服务

Android中的HCE体系结构基于Android Service组件(称为“HCE服务”)。服务的一个关键优势是它可以在没有任何用户界面的情况下在后台运行。这对于许多HCE应用程序来说非常合适,例如会员卡或公交卡,用户不需要启动应用程序即可使用它。相反,通过NFC读卡器轻敲设备将启动正确的服务(如果尚未运行)并在后台执行该事务。当然,如果有意义的话,您可以自由地从您的服务中启动额外的UI(例如用户通知)。

4.1 服务选择

当用户将设备连接到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,因为它可以避免与其他应用程序发生冲突。

4.2 AID组

在某些情况下,HCE服务可能需要注册多个AID才能实现某个应用程序,并且需要确保它是所有这些AID的默认处理程序(而不是组中的某些AID转到其他服务) 。

一个AID组是应该被OS视为一起归属的AID列表。对于AID组中的所有AID,Android会保证以下其中一项:

  • 组中的所有AID都路由到此HCE服务
  • 该组中的任何AID都不会路由到此HCE服务(例如,因为用户更喜欢另一个在您的组中也请求一个或多个AID的服务)

换句话说,没有中间状态,组中的一些AID可以路由到一个HCE服务,另一些AID可路由到另一个。

4.3 AID组和类别

每个AID组都可以与一个类别关联。这允许Android按类别将HCE服务组合在一起,并且反过来又允许用户在类别的级别而不是AID级别设置默认值。通常,避免在应用程序的任何面向用户的部分提及AID:它们对普通用户没有任何意义。

Android 4.4支持两种类别: CATEGORY_PAYMENT(涵盖行业标准支付应用程序)和CATEGORY_OTHER(对于所有其他HCE应用程序)。

注意:CATEGORY_PAYMENT在任何给定时间,只能在系统中启用该类别中的一个AID组。通常,这将是一款了解主要信用卡付款协议并可以在任何商家工作的应用程序。

对于仅适用于一个商家(如储值卡)的闭环支付应用程序,您应该使用CATEGORY_OTHER。此类别中的AID组可以始终处于活动状态,并且在需要时可以在AID选择期间由NFC读卡器给予优先权。

0x05 实施HCE服务

要使用基于主机的卡仿真来模拟NFC卡,您需要创建一个Service处理NFC事务的组件。

5.1 检查HCE支持

您的应用程序可以通过检查FEATURE_NFC_HOST_CARD_EMULATION功能来检查设备是否支持HCE 。您应该<uses-feature>在应用程序清单中使用该标记来声明您的应用程序使用HCE功能,以及该应用程序是否需要运行。

5.2 服务实施

Android 4.4带有一个便利的Service类,可以作为实现HCE服务的基础:HostApduService类。

因此,第一步要扩大HostApduService

1
2
3
4
5
6
7
8
9
10
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作为回报。

注: ISO / IEC 7816-4规范还定义了多个逻辑信道的概念,您可以在单独的逻辑信道上进行多个并行APDU交换。Android的HCE实现只支持单个逻辑通道,所以只有单线程交换APDU。

如前所述,Android使用AID来确定读者想要与哪个HCE服务交谈。通常,NFC读卡器向您的设备发送的第一个APDU是“SELECT AID”APDU; 这个APDU包含读卡器想与之交谈的AID。Android从APDU中提取AID,将其解析为HCE服务,然后将该APDU转发给已解析的服务。

您可以通过返回响应APDU的字节来发送响应APDU processCommandApdu()。请注意,此方法将在应用程序的主线程中调用,该线程不应被阻止。所以如果你不能立即计算并返回一个响应APDU,那么返回null。然后,您可以在另一个线程上完成必要的工作,并sendResponseApdu()在完成后使用HostApduService该类中定义的方法发送响应。

Android会继续将新的APDU从读取器转发到您的服务,直到:

  1. NFC读卡器发送另一个“SELECT AID” APDU,OS将其解析为不同的服务;
  2. NFC读卡器和您的设备之间的NFC链接被破坏。

在这两种情况下,你的类的 onDeactivated()实现都是通过一个参数来调用的,这个参数指出了两者中的哪一个发生了。

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

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

5.3 服务清单声明和AID注册

您的服务必须像往常一样在清单中声明,但还必须在服务声明中添加一些附加件。

首先,为了告诉平台它是一个实现HostApduService接口的HCE服务 ,你的服务声明必须包含一个SERVICE_INTERFACE动作的Intent Filter

另外,为了告知平台哪个AIDs组被这个服务请求,一个SERVICE_META_DATA<meta-data>标签必须包含在服务的声明中,指向一个XML资源和关于HCE服务的附加信息。

最后,您必须将该android:exported属性设置为true,并且"android.permission.BIND_NFC_SERVICE"在服务声明中要求权限。前者确保服务可以被外部应用程序绑定。后者然后强制只有拥有该"android.permission.BIND_NFC_SERVICE"权限的外部应用程序 才能绑定到您的服务。既然"android.permission.BIND_NFC_SERVICE"是一个系统权限,这有效地强制只有Android OS可以绑定到你的服务。

这是一个HostApduService清单声明的例子:

1
2
3
4
5
6
7
8
<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组声明的此类文件的示例:

1
2
3
4
5
6
7
8
9
<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 or 定义的字符串常CATEGORY_OTHER
  • 每个标签<aid-group>必须包含一个或多个 <aid-filter>标签,每个标签包含一个AID。AID必须以十六进制格式指定,并且包含偶数个字符。

最后,您的应用程序还需要拥有NFC可以注册为HCE服务的 权限。

0x06 AID冲突解决

多个HostApduService组件可以安装在单个设备上,并且可以由多个服务注册相同的AID。Android平台根据AID属于哪个类别来解决AID冲突。每个类别可能有不同的冲突解决策略。

例如,对于某些类别(如付款),用户可能能够在Android设置UI中选择默认服务。对于其他类别,策略可能总是要求用户在冲突情况下调用哪个服务。要查询特定类别的冲突解决策略,请参阅 getSelectionModeForCategory()

6.1 检查您的服务是否为默认设置

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

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

0x07 付款应用

Android会将AID组为“payment”的类别,声明的HCE服务视为支付应用程序。Android 4.4版本包含一个名为“tap&pay”的top-level设置菜单条目,它列举了所有这些支付应用程序。在此设置菜单中,用户可以选择在点按付款终端时将调用的默认支付应用程序。

7.1 支付应用程序所需的resource

为了提供更具视觉吸引力的用户体验,HCE支付应用程序需要为其服务提供额外的resource:所谓的服务标记。

这个asset的大小应该是260x96 dp,并且可以在元数据(meta-data)XML文件中通过添加指向drawable resourceandroid:apduServiceBanner<host-apdu-service>标签的属性来指定 。一个例子如下所示:

1
2
3
4
5
6
7
8
9
10
<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>

0x08 屏幕关闭和锁屏行为

当设备的屏幕关闭时,当前的Android实施将NFC控制器和应用程序处理器完全关闭。因此,当屏幕关闭时,HCE服务将无法工作。

然而,HCE服务可以从锁定屏幕中起作用:这由HCE服务标记中的android:requireDeviceUnlock属性控制<host-apdu-service>。默认情况下,不需要设备解锁,即使设备被锁定,您的服务也会被调用。

如果您将android:requireDeviceUnlockHCE服务的属性设置为“true”,Android会提示用户在您靠近NFC读卡器时解锁设备,NFC读卡器会选择已解析为您的服务的AID。解锁后,Android会显示一个对话框,提示用户再次点击以完成交易。这是必要的,因为用户可能已经将设备从NFC读卡器移开以便解锁它。

0x09 与SE卡共存

本部分对于已经部署依赖SE进行卡模拟的应用程序的开发人员很感兴趣。Android的HCE实现旨在与其他实现卡仿真的方法并行工作,包括使用SE。

注意: Android不提供用于直接与SE进行通信的API。

这种共存基于一种称为“AID路由”的原则:NFC控制器保留一个由(有限)路由规则列表组成的路由表。每个路由规则都包含一个AID和一个目的地。目标可以是主机CPU(Android应用程序正在运行的地方),也可以是连接的SE。

当NFC读卡器发送具有“SELECT AID”的APDU时,NFC控制器解析它并检查AID是否与其路由表中的任何AID匹配。如果匹配,那么APDU和其后的所有APDU将被发送到与AID相关联的目的地,直到收到另一个“SELECT AID” APDU或NFC链路断开。

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

图4说明了这种架构。

图4.使用SE和主机卡模拟的Android操作

NFC控制器通常还包含APDU的默认路由。在路由表中找不到AID时,将使用默认路由。尽管此设置可能因设备而异,但Android设备需要确保您的应用注册的AID已正确路由到主机。

实现HCE服务或使用SE的Android应用程序不必担心配置路由表 - 这是由Android自动处理的。Android只需要知道哪些AID可以由HCE服务处理,哪些可以由SE处理。基于哪些服务已安装,以及哪些用户已配置为首选服务,路由表会自动配置。

我们已经介绍了如何声明HCE服务的AID。以下部分说明如何为使用SE进行卡模拟的应用程序声明AID。

9.1 Secure Element AID注册

使用SE进行卡模拟的应用程序可以在其清单中声明所谓的“关闭主机服务”。这种服务的声明几乎与宣布HCE服务相同。以下情况例外:

  • 意图过滤器中使用的操作必须设置为 SERVICE_INTERFACE

  • 元数据名称属性必须设置为 SERVICE_META_DATA

  • 元数据XML文件必须使用<offhost-apdu-service>根标签。

    1
    2
    3
    4
    5
    6
    7
    8
    <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>

相应apduservice.xml文件注册两个AID 的示例:

1
2
3
4
5
6
7
<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不参与事务,因此无法阻止SE在设备锁定时执行事务。

android:apduServiceBanner属性必须用于作为支付应用程序的关闭主机服务,以便作为默认支付应用程序进行选择。

9.2 关闭主机服务调用

Android本身永远不会启动或绑定到声明为“脱离主机”的服务。这是因为实际交易由SE执行,而不是由Android服务本身执行。服务声明仅允许应用程序注册安全元件(Secure Element)上存在的AID。

0x0A HCE和安全

HCE体系结构本身提供了一个核心安全性:因为您的服务受到BIND_NFC_SERVICE系统权限的保护,所以只有操作系统可以绑定到您的服务并与之通信。这可以确保您收到的任何APDU实际上都是OS从NFC控制器接收到的APDU,并且您发回的任何APDU只会发送到操作系统,而操作系统会直接将APDU转发给NFC控制器。

剩下的核心部分就是您获取应用程序发送给NFC读卡器的数据的位置。这在HCE设计中有意解耦:它不关心数据来自何处,它只是确保将其安全地传送到NFC控制器并传送到NFC读取器。

为了安全地存储和检索您希望从HCE服务发送的数据,例如,您可以依靠Android应用程序沙箱,将应用程序的数据与其他应用程序隔离。有关Android安全性的更多详细信息,请阅读 安全提示

0x0B 协议参数和细节

这部分内容对于希望了解HCE设备在NFC协议的防冲突和激活阶段使用何种协议参数的开发人员很感兴趣。这允许构建与Android HCE设备兼容的读卡器基础结构。

11.1 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进行比较。

11.2 ISO-DEP激活

Nfc-A协议激活后,NFC读取器启动ISO-DEP协议激活。它发送一个“RATS”(请求选择应答)命令。RATS响应(ATS)完全由NFC控制器生成,不能由HCE服务配置。然而,HCE实现需要满足NFC论坛对ATS响应的要求,因此NFC读卡器可以根据NFC论坛对任何HCE设备的要求设置这些参数。

以下部分提供了有关NFC控制器在HCE设备上提供的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,也可能不支持DID。位1表示支持NAD。HCE设备不能支持NAD并将位1设置为零。
  • 历史字节:HCE设备最多可以返回15个历史字节。愿意与HCE服务交互的NFC读卡器不应该假设历史字节的内容或它们的存在。

请注意,许多HCE设备可能符合EMVCo联合的支付网络在其“非接触式通信协议”规范中指定的协议要求。尤其是:

  • T0中的FSCI必须在2小时和8小时之间。
  • T(A)1必须设置为0x80,表示仅支持106 kbit / s比特率,并且不支持读卡器和仿真器之间的非对称比特率。
  • T(B)1中的FWI必须<= 7h。

11.3 APDU数据交换

如前所述,HCE实现仅支持单个逻辑通道。尝试在不同的逻辑通道上选择应用程序将不适用于HCE设备。

0x0C 后记

本文翻译自谷歌开发者文档,已由本人仔细校对。如有错误,请联系我,以便修改。

文章目录
  1. 1. 0x00 概述
  2. 2. 0x01 卡仿真与SE
  3. 3. 0x02 基于主机的卡模拟
  4. 4. 0x03 支持的NFC卡和协议
  5. 5. 0x04 HCE服务
    1. 5.1. 4.1 服务选择
    2. 5.2. 4.2 AID组
    3. 5.3. 4.3 AID组和类别
  6. 6. 0x05 实施HCE服务
    1. 6.1. 5.1 检查HCE支持
    2. 6.2. 5.2 服务实施
    3. 6.3. 5.3 服务清单声明和AID注册
  7. 7. 0x06 AID冲突解决
    1. 7.1. 6.1 检查您的服务是否为默认设置
  8. 8. 0x07 付款应用
    1. 8.1. 7.1 支付应用程序所需的resource
  9. 9. 0x08 屏幕关闭和锁屏行为
  10. 10. 0x09 与SE卡共存
    1. 10.1. 9.1 Secure Element AID注册
    2. 10.2. 9.2 关闭主机服务调用
  11. 11. 0x0A HCE和安全
  12. 12. 0x0B 协议参数和细节
    1. 12.1. 11.1 Nfc-A(ISO / IEC 14443 A型)协议防冲突和激活
    2. 12.2. 11.2 ISO-DEP激活
    3. 12.3. 11.3 APDU数据交换
  13. 13. 0x0C 后记
,