PAYATHON 2026

如何在移动设备的 secure element 中读写 NFC 支付数据?

支付老李

结论

普通移动应用既不能把银行卡号、有效期、CVV、磁条数据或支付密钥直接写入 secure element,也不应在支付时读取这些数据后再交给 NFC。

NFC 支付通常按以下方式完成:

  1. 发卡行或受信任的支付服务商将支付凭证安全配置到 secure element,或者通过 HCE(Host Card Emulation)配合云端令牌化实现支付。
  2. POS 终端通过 NFC 发送 EMV/APDU 命令。
  3. secure element 中的支付 applet 使用内部密钥生成交易响应。
  4. NFC 控制器将响应返回给 POS。
  5. 支付密钥通常始终保留在 secure element 内。业务应用只能选择默认卡、展示脱敏信息或发起受控操作。

所以,“NFC 读取卡片详细信息并传给 POS”这个说法并不准确。实际情况是,POS 与支付 applet 按协议交互,获取完成本次交易所需的 EMV 数据和动态密码,而不是读取一份完整的银行卡资料。

secure element 能保存多少张卡

目前没有统一的”最多几张卡”标准。具体容量取决于:

  • secure element 可用的非易失性存储空间;
  • 支付 applet、密钥和证书占用的空间;
  • GlobalPlatform 安全域的配置;
  • 设备厂商、运营商、钱包服务商和发卡行的策略;
  • 各支付网络的认证及生命周期管理要求。

即使两台设备采用容量相同的 secure element,能够配置的卡片数量也可能不同。实际产品中,钱包可以添加多少张卡,通常取决于平台或钱包服务设定的产品限制,不能根据 secure element 的标称存储容量直接计算。

还需要区分”钱包中显示的卡”和”secure element 中存在的支付实例”。有些卡采用 HCE 或云端令牌,有些可能只是展示记录,并不包含可用于非接触支付的本地凭证。

数据是否经过加密

不能简单地认为 secure element 中的所有记录都按某种统一格式”加密保存”。

secure element 提供隔离执行、访问控制、防篡改能力和受保护的密钥存储。数据是否需要额外加密,以及使用哪种密钥和算法,要由支付 applet、GlobalPlatform 配置和支付网络规范决定。

通常可以确认以下几点:

  • 支付密钥不会以明文形式提供给普通应用;
  • applet 之间通过安全域和访问规则隔离;
  • 配置卡片时通常会使用经过认证的安全通道,例如 GlobalPlatform Secure Channel;
  • 交易密码由 applet 在内部生成,调用方通常只能获得运算结果;
  • CVV、PIN、磁条 Track 1/Track 2 等敏感认证数据不能作为普通业务数据随意保存。

secure element 不会让原本不合规的卡数据存储方式自动变得合规。系统仍须满足 PCI DSS、PCI MPoC,以及相关支付网络和发卡行的安全要求。

如何选择指定支付卡

正确的做法不是读取某张卡的明文资料,而是选择相应的支付凭证或支付 applet。

常见方案有三种。

由系统钱包管理默认卡

Apple Pay、Google Wallet 和设备厂商钱包通常由系统统一管理默认支付卡、用户认证及 NFC 路由。第三方应用只能通过平台提供的支付接口请求支付,无法直接读取钱包中的卡凭证。

这是普通 e-payment 应用最常见的实现方式。

由钱包服务管理多个 applet

在受控的钱包架构中,每个支付产品可能对应一个 applet 实例或令牌化账户。钱包应用通常只保存非敏感元数据,例如:

  • 内部卡片标识;
  • 卡片品牌;
  • 末四位;
  • 卡面样式;
  • applet 或支付产品的引用标识;
  • 是否为默认支付卡。

用户选择卡片后,钱包会通过平台认可的机制更新默认支付凭证或 contactless 路由。相关接口通常只对设备厂商、支付网络、运营商或获得授权的钱包服务商开放。

使用 HCE

在 Android 上,应用可以实现 HostApduService,由 NFC 控制器将指定 AID 的 APDU 命令路由到应用。此时,应用模拟的是一张非接触卡,但敏感支付凭证仍应使用支付令牌、短期密钥或受硬件保护的密钥,不能将真实卡片密钥直接存入应用数据库。

下面是一个简化的 APDU 路由示例:

<service
    android:name=".PaymentHostApduService"
    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>
<!-- res/xml/apduservice.xml -->
<host-apdu-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/payment_service"
    android:requireDeviceUnlock="true">

    <aid-group
        android:category="payment"
        android:description="@string/payment_aid_group">
        <aid-filter android:name="F0010203040506" />
    </aid-group>
</host-apdu-service>
class PaymentHostApduService : HostApduService() {

    override fun processCommandApdu(
        commandApdu: ByteArray,
        extras: Bundle?
    ): ByteArray {
        // 这里只负责协议状态机和受控的令牌操作。
        // 不应在此返回 PAN、CVV、PIN 或长期支付密钥。
        return byteArrayOf(0x6A.toByte(), 0x82.toByte())
    }

    override fun onDeactivated(reason: Int) {
        // 清除本次会话的临时状态。
    }
}

F0010203040506 只是演示用 AID,6A82 表示未找到请求的文件或应用。这段代码不能直接用于银行卡支付。真正的 EMV 非接触支付还需要支付网络认证、完整的 APDU 状态机、令牌生命周期管理、风控和密钥体系,不能靠拼装几个 APDU 响应来实现。

应用能否直接访问 secure element

部分 Android 设备提供 Open Mobile API(OMAPI),例如:

SEService(context, executor) { service ->
    val reader = service.readers.firstOrNull {
        it.name.contains("eSE", ignoreCase = true)
    } ?: return@SEService

    val session = reader.openSession()
    val aid = byteArrayOf(
        0xF0.toByte(), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06
    )

    val channel = session.openLogicalChannel(aid)
    val response = channel.transmit(
        byteArrayOf(
            0x00, 0xA4.toByte(), 0x04, 0x00, 0x00
        )
    )

    channel.close()
    session.close()
}

这段代码仅用于说明 OMAPI 的调用结构,不表示应用能够访问银行卡 applet。实际访问会受到 secure element access rules、证书签名、设备策略和发行方授权的限制。普通第三方应用通常无法打开系统钱包的支付 applet,也不能通过枚举 AID 绕过这些限制。

另外,OMAPI 通道用于应用与 secure element 通信。POS 执行 tap and pay 时,使用的是 NFC 卡模拟和控制器路由,两者并非同一个调用流程。

Tap and Pay 的实际数据流

典型流程如下:

  1. 用户选择默认卡,并按平台要求完成解锁或生物识别认证。
  2. 手机靠近 POS 后,POS 开始轮询并激活 NFC 卡模拟接口。
  3. POS 发送应用选择命令,例如选择支付环境或特定 AID。
  4. NFC 控制器将命令路由到 secure element applet 或 HCE 服务。
  5. 支付 applet 根据金额、终端数据、交易计数器和随机数生成 EMV 响应。
  6. POS 将交易数据提交给收单机构,再由支付网络和发卡行完成授权。

传输内容可能包括支付令牌、应用交易计数器、动态应用密码,以及协议要求的其他数据。有些字段可能并非保密数据,但会受到完整性保护,而且这些字段的组合方式受支付规范严格约束。

NFC 主要负责近距离通信和命令路由。它不会主动进入 secure element 查找某张卡,也不负责决定使用哪张卡。卡片选择、AID 路由、用户认证和交易授权分别由系统钱包、NFC 控制器、支付 applet 及后端服务共同完成。

推荐的实现步骤

如果要开发能够实际上线的 NFC 支付应用,可以按以下顺序设计:

  1. 确定采用系统钱包、设备厂商钱包、HCE,还是自有 secure element 方案。
  2. 与发卡行、令牌服务提供商和支付网络确认准入及认证要求。
  3. 用支付令牌替代真实 PAN,并建立令牌的配置、暂停、恢复和删除流程。
  4. 分开存储卡片展示信息和支付凭证,应用侧只保留必要的脱敏元数据。
  5. 通过平台提供的默认支付服务和 AID 路由机制选择卡片。
  6. 在 applet、硬件密钥库或合规后端中完成密码学运算,不向应用暴露长期密钥。
  7. 实现设备绑定、用户认证、重放防护、交易计数器和风险控制。
  8. 完成实验室测试、支付网络认证、PCI 评估及设备兼容性测试后,再投入生产。

如果只是给现有应用增加 NFC 支付功能,应优先集成系统钱包或支付服务商 SDK。自行向 secure element 写入银行卡凭证,不仅缺少通用的公开接口,还涉及发卡授权、密钥注入、硬件合作和支付网络认证,通常不是普通应用可以独立完成的。

备注:内容仅供参考。