随着物联网与O2O业务的发展,NFC在小额支付领域的前景越来越广阔。本文结合多个公开文档,简要介绍了NFC的工作场景,同时使用RxAndroid实现了对NFC字节流的读取与异常捕捉;使用了MVP(好吧,其实是一个简单的静态代理)转移了Activity下的代码量;介绍了卡模拟过程中与Android底层的交互原理与简单示例。
1. NFC工作模式
1.1. 读卡器模式
直接操作“NFC Tag”对象对实体卡片进行I/O操作,目前的NFC卡片大多都是运行Applet服务的嵌入式JAVA虚拟机(俗称CPU卡,比较贵,这个卡的性能非常弱,只能进行写改删查操作),我们通过AndroidAPI连接后,就可以使用各种字节命令(APDUS)控制卡片。比如网上的“公交卡充值/查询”APP就是与公交公司合作的读卡器。
1.2. 点对点模式
使用 Android Bean
技术进行点对点通信,NFC本身速度很慢,所以这项技术最多的场景就是通过两台同品牌手机的NFC私有的应用协议数据单元(APDU)配对,然后建立高速的蓝牙/WIFI通信进行数据交换,以节约适配蓝牙/WIFI的时间。比如蓝牙耳机QC35,华为路由器就支持NFC快速配对。
1.3. 卡模拟模式
这个场景下,我们可以把手机看成一张“公交卡/银行卡”。真正的应用过程在上级应用层与物理加密芯片之间。建行的"Quick Pass"/ApplePay等技术就是基于此的。
1.3.1. 虚拟卡模式(Virtual Card Mode)
某些手机内置了安全芯片(SE,Secure Element),比如
- SD卡(银联主推)
- 手机内置(Embeded,终端厂商主推)
- SIM卡内置(运营商主推),经过测试我的电信4G天翼卡就是支持卡模拟的
这些芯片的内部实际上运行一个微型JAVA虚拟机,,一个卡(手机)可以装多个applet,它有自己的证书,上层协议是卡片与芯片内置的Applet进行加密交互。通过OAT(空中发卡)业务可以实现把服务器中的Applet二进制文件下载到手机芯片中(是不是有点像HotFix技术?),俗称“卡包”。
举个例子,我通过软件下载了“深圳通”“招商银行”两个applet到手机内置芯片中。当我要刷公交卡时,公交车上的机器将进行如下操作:
1 | select 公交卡applet from applets |
它最大的好处就是写入了Applet后,手机没电了同样可以刷卡使用(取决于电路设计)。而且它支持多张卡,节约空间。
1.3.2. 主机卡模式(Host Card Mode)
在Android文档中可以看到,谷歌在Kitkat后,提供了一种HCE(Host Card Emulation)的方法,只要在Android开发中继承HostApduService
服务(类似于J2EE中的Applet),就可以实现软件上进行卡模拟,当然这类的应用层的安全性非常重要,最终还是通过云服务的一些Token/证书进行交互。以后移动互联网公司可能会在这个地方发力。
1 | CardReader <----> NFCAdapter <----> HostApduService <----> BackendCloudServer |
2. NFC的传输层协议栈
JAVA中
byte
是有符号一字节的,而char
是编码过的两个字节的;C中byte(也就是#define byte (unsigned char))
是无符号一个字节的,而char
是有符号一字节的; 为了方便,我们全部使用byte与16进制进行表示
APDU的数据结构如图,本质是一种编码,网上有很多序列化/反序列化的工具
发送的数据报
返回值是
3. NFC读卡器的Android开发
3.1. 配置Manifest
配置权限与feature
1 | <!--Use NFC feature and Permissions--> |
注意配置Activity的模式为singleTop
,配置Activity不可转变屏幕(防止Intent丢失,支付宝也是这样做的),配置NFCTech过滤器,配置Intent接收器
1 | <activity |
配置NFC卡片技术过滤器,每个卡对应一个<tech-list>
1 | <!--file nfc_tech_filter.xml --> |
3.2. 获取Intent
在Activity中针对收到的Intent进行处理,onCreate是从其他App进入的情况,onNewIntent是已经在本App中收到新的Intent的情况
1 | protected void onCreate(Bundle savedInstanceState) { |
接下来处理Intent,获取到Tag对象
1 | private void handleIntent(Intent intent) { |
3.3. I/O处理
接下来,我们通过Tag获取到I/O对象,通过字节流进行处理,这里的例子按照如下的标准进行解析
1 | ISO-DEP (ISO 14443-4) 注意它与ISO-7816也是兼容的 |
目前最完善的代码如下,基于RxAndroid进行异步处理,可以处理所有的异常,注意这里的subscriber
是一个RxAndroid
的回掉接口,它处理成功
,下一个
,异常
这三个回掉
1 | public final Observable<ResponseAPDU> getResponseAPDUObservable(final Tag tag, |
在UI线程下,进行如下的调用
1 | //RxAndroid的回掉 |
这里还有很大的优化空间,比如加入 keep-alive 机制,避免每读写一次都重新打开关闭流,当然这个需要与场景进行适配
3.4. 封装成高级对象(DTO)
通过以上步骤,我们能够得到一个简单的基于字节流的NFC读取器,但是更加进一步的开发(比如查询命令,SDK等)需要卡厂商提供内部公开的SDK,均有私钥的,就不能具体写了。有兴趣的可以去网上搜索PBOC2.0
,网上有泄露的代码,貌似是GPL协议,各位慎重学一下。这个开发一点都不难,封装好I/O后,写改删查即可,主要难点在能够与公交公司谈拢(人家说不定看不上你,当然这个活不是程序员干的),以及谈拢后与对方开发进行联调。
4. NFC的卡模拟
从12年开始,随着O2O的发展,国外的品牌手机都开始加入了对SE芯片的支持,中华酷联也开始加入了对SE卡的支持。
4.1. 判断手机是否支持SE
手机中必须有如下的文件,这是一个开源的项目,各大手机厂商都毫不吝啬的使用了,注意SmartcardService需要在后台运行才能调用,某些ROM被精简了,12年以后的高档机(比如三星,Sony大法)到现在的千元机(比如魅蓝NOTE)的ROM中一般都有这个文件
1 | /system/framework/org.simalliance.openmobileapi.jar |
如果没有的话,会报错ClassNoFoundException,这里我也没有比较好的解决方案,建议先放着,优先把支持的机子给适配好。
4.2. AndroidSE的通信架构
经过阅读开源代码,交互流程如下,NFC卡模拟开发实际上是基于Binder的,对AIDL与Binder通信需要有理解。
1 | Activity(调用者) |
上文中的 :remote
实际上就是 Stub.asInterface()
实现远程打桩的代理调用,类似于Spring中的依赖查找。当然在SDK端,只用写AIDL即可,甚至SDK都被写好了。
Binder机制实际上是基于
ASHMEM(匿名共享内存)
的IPC
4.3. 开发卡模拟
在AndroidStudio中,需要注意jar包的依赖形式,由于这个包是已经在系统框架中使用了,防止classloader重复加载,所以这个jar包不能打包到app中,只用在编译时设置为provided
即可
1 | dependencies { |
剩下的事情都交个openmobileapi的SDK了,SDK提供了基于字节流的通信,我们可以使用okio这样的工具来转换字节与string,当然更高级的使用涉及到厂商的私钥,一般通过TSM发卡厂商提供的私有SDK与密钥进行更高层的封装(反正这些都有SDK),就完成开发了。
注意两点:
- 回调是在Binder线程池中,所以注意谨慎操作UI组件
- onDestory时注意释放连接
常见金融协议
- PBOC3.0: 国内银联协议
- EMV: MasterCard, VISA, JCB和国际银联等
5. Source Code
代码详见我的Github示例,只保留了基于字节流的查询功能。
NFC开发是大坑,适配很累,而且在第三方联调上很费时间,属于那种吃力不讨好最后又啥都没学到的。我不建议深入折腾