2013-10-10 编写

前言

在“十问Android NFC手机上的卡模拟”文中仅仅简单的介绍了一下相关的概念,如果需要了解基于SE的卡模拟的更多细节,也就是,究竟在Android的NFC手机上,目前能够做到何种程度的卡模拟,以及如何实现,则需要更深入的讨论。

我们已经了解,NFC RF模块可以支持卡模拟工作方式,而且可以通过两种方式实现卡模拟,一种是基于硬件的,被称为虚拟卡模式(Virual Card Mode);一种是基于软件的,被称为主机卡模式(Host Card Mode)。无论哪种方式,都是NFC RF模块将外部读写器的指令转发到相关的处理模块,SE或手机上的应用程序,然后将回复信息发回外部读写器。

本文不讨论基于软件的方式,因为在Android中,必须修改相关固件以支持该功能,也就是必须使用第三方ROM,例如Cyanogenmod。本文的重点是,如果使用硬件SE的方式,我们是否能够做到:

1,  从手机内部访问SE,建立手机应用程序与SE之间的通讯连接并发送命令。

2,  将NFC模块和SE置于卡模拟工作方式,使用外部读写器中的命令转向SE。

3,  在SE中安装自己的应用,实现最终的卡模拟。

这里先预告一下本文的结论,以免浪费大家的时间,在目前看来,没有SE密钥的用户,只能在特定条件下实现功能1和2,而功能3则是不可能的。而功能1和2的条件对一般用户也是非常苛刻的,包括

  • l  手机支持NFC,并且SE为内置SE或SWP-SIM,
  • l  手机已经ROOT,
  • l  Android版本在Android 4.0.4 (API Level 15)上,而且因为用到了一些未公开类,所以不能保证在今后的版本中还能使用。(经测试这些未公开类在目前最新版本4.3中还可以工作)。

当然,深入讨论需要更多的专业知识,包括Android编程,智能卡等,虽然不需要全部精通,但至少有所了解。

SE硬件形态

SE是一个CPU卡,可以运行智能卡应用程序(称为小应用或卡应用)。一个智能卡从本质上讲就是在单一芯片上的微型计算环境,具有完备的CPU,ROM,EEPROM,RAM和I/O接口。一般智能卡还具有密钥算法协处理器,可以支持常用的加解密算法,例如DES,AES和RSA等。智能卡通过多种技术实现抗攻击特性,很难通过分解或分析芯片提取数据。事实上手机用户对SE都不陌生,因为手机的SIM卡本身就是一个SE(技术上讲只能在GSM中叫做SIM卡,更通用的应该叫做UICC)。

SE可以有多种集成形态:UICC,内置SE或在SD插槽上的插卡。本文主要讨论内置SE的方式,但首先简要了解一下其它形态的SE。

  • l  UICC形式的SE

普通的UICC仅仅和手机中的基带处理器相连,但基带处理器与运行Android的应用处理器是分离的,因此不能通过Android应用程序直接访问。所有的通讯需要通过射频界面层Radio Interface Layer (RIL),这是与基带处理器的IPC界面。UICC SE的通讯基于扩展AT命令 (AT+CCHO,AT+CCHC, AT+CGLA等),在目前Android中的telephony manager不支持。目前还有没有能够通过RIL访问UICC SE的标准方式(尽管有些带有定制化固件的商业设备据说支持这种方式),因为这个原因,普通的UICC并不适合NFC应用。还有一种方法是使用Single Wire Protocol (SWP)方式,SWP类型的UICC通过SWP连接到NFC控制器,目前很多移动支付都使用该方式,只要手机支持NFC功能,就可以通过更换UICC实现移动支付应用。

  • l  SD卡形式

另一种形态是AdvancedSecurity SD card (ASSD),本质上是一个带有嵌入式SE芯片的SD卡。将SD卡插入Android设备SD插槽,并运行一个SEEK补丁过的Android版本,可以通过SmartCard API访问SE。但是并不是所有手机都具有SD插槽,因此ASSD方式不太可能成为主流。

  • l  内置SE模式

正如其名,内置SE是设备主板的一部分,并作为NFC芯片的专用芯片,或者干脆集成为NFC芯片的一部分,因此内置SE不能从手机上移除。第一个支持内置SE的设备是Nexus S,这款手机也是首款支持NFC的Android手机。我们实验用的设备,Galaxy Nexus,带有内置的NXPPN65N 芯片,该芯片在一个单独的封装中集成了一个NFC射频控制器和一个SE(NXPSmartMX系列的P5CN072)。下图为P5CN081的硬件架构图,由于没有找到P5CN072的图,用P5CN081代替,它们之间的区别仅仅是EEPROM大小不同。

P5xyzzz SmartMX 型号命名

x 产品类型:

C= PKI 控制器 + 3-DES 协处理器 + AES协处理器

y 接口类型:

C= 接触界面 - ISO/IEC 7816

D= 接触和非接触双界面 - ISO/IEC 7816 +ISO/IEC 14443 contactless interface

N= ISO/IEC 7816 + S2C NFC接口

zzz非易失存储器大小,单位KB

SE与手机的连接

由于SE的硬件形态很多,因此从手机应用程序访问起来也有很多不同的路径。所幸Android下的SEEK(Secure Element Evaluation Kit  https://code.google.com/p/seek-for-android/)项目试图为开发人员屏蔽这些不同的硬件类型,提供一个统一的访问接口。感兴趣的可以去参考一下,本文使用SEEK的架构图说明不同SE的不同访问途径。

从手机访问SE被称为在WIRED CARD 模式,可以分别通过NFC API, ASSD, RIL访问Ese(包括SWPSIM), µSD和SIM(非SWPSIM)类型的SE,同时通过第三方驱动支持外置模块SE。

从图中可以看到:

1, 内置SE(eSE)                        手机APP – NFC LIB – CLF-内置SE

2, SWP-UICC                           手机APP – NFCLIB – CLF-SWP-UICC

3, 普通UICC                             手机APP – RIL –基带处理器 –UICC

4, SD卡(ASSD)                   手机APP – ASSD -µSD

5, 第三方外置模块(PLUGIN)            手机APP –外置模块驱动 -外置模块SE

其中CLF为ContactlessFrontend的缩写,一般指NFC RF模块和NFC天线。为了简化测试程序,本文并没有使用SEEK库,而仅仅使用Android中未公开的访问类,因此只能对内置SE和SWP-UICC进行测试。

在手机中通过连线模式访问SE

内置式SE或SWP UICC分别通过SignalIn/SignalOut接口(S2C,即NFCWI)和SWP接口与NFC控制器连接,具有三种操作模式:关闭,连线和虚拟。

  • l  关闭模式,NFC CLF模块与SE没有通讯
  • l  连线模式,NFC CLF模块使SE对Andorid操作系统可见,就像与RF读写器连接的(非接触式)智能卡
  • l  虚拟模式,NFC CLF模块使SE对外部读写器可见,这时手机就像一个非接触智能卡

这三种模式本质上是互斥的,因此我们可以通过外部非接触界面与SE通讯(例如外部读写器),或者通过内部连线接口访问(例如通过Android上的应用程序),但无法同时使用。

在手机开机后,缺省状态下,SE是处于关闭状态。因此为了实现卡模拟,将SE置于虚拟模式,并在这三种模式之间自由切换,我们必须先实现在手机内部对SE的访问。

我们在前面提到,实现手机内部应用程序对SE的访问需要几个苛刻的条件,现在就具体解释一下为什么需要这些条件

1,  为什么需要支持内置或SWP-UICC的SE的NFC手机

NFC模块就不用说了,没有NFC模块,就不可能实现外部的读写器访问。为什么需要内置或SWP-UICC形式的SE在上面也有说明。

2,  为什么要求Android版本高于Android4.0.4 (API Level 15)?

在Android 2.3.4中引入了访问内置SE的内部API,并在这个版本上发布了谷歌钱包。但这些API依然在SDK中隐藏,另外在2.3.4和接下来的Gingerbread发布版中,还需要系统级权限(WRITE_SECURE_SETTINGS或NFCEE_ADMIN)才能使用这些API。早期的Ice Cream Sandwich发布版(4.0, API Level 14)中也是如此。这就意味着只有谷歌(对Nexus手机)和手机制造商(对他们自己品牌的手机)才能发布使用SE的应用程序,因为他们要么能够访问操作系统核心,要么能够拥有硬件平台密钥。但在Android 4.0.4 (API Level 15)中使用签名证书代替了系统级的权限许可(也就是在Android架构术语中的签名),因此,只要在Android系统上进行白名单登记,不需要制造商密钥,就可以访问内置的SE,这就大大简化了发布流程。另外,由于签名在白名单中文件中保存,这就可以通过OTA方式更新该列表,以便添加使用SE的应用程序。

3,  为什么要求ROOT

上面提到的白名单文件就是/etc/nfcee_access.xml,该文件是一个XML格式的文件,保存了允许访问SE的包名称和签名证书列表。下面是该文件的示例:

<?xmlversion="1.0" encoding="utf-8"?>

<resourcesxmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">

<signerandroid:signature="30820...90">

<package android:name="com.example.embeddedseaccess">

</package></signer>

</resources>

例子中表明允许'com.example.embeddedseaccess' 包访问SE。因此允许应用访问SE的第一步就是在nfcee_access.xml中添加签名证书和包名。该文件位于系统分区(/etc 是 /system/etc的符号链接),因此我们需要root权限,以便在读写模式下修改该文件。

开始编程

满足上述条件后,我们可以开始实际的编程工作,建立访问SE的应用程序

1,  在AndroidManifest.xml添加必要的权限和库

<uses-permissionandroid:name="android.permission.NFC" />

<uses-library

android:name="com.android.nfc_extras"

android:required="true" />

2,  使用未公开的类

在配置完这些文件后,终于可以使用SEAPI的时候了。目前Android并没有实现标准的智能卡通讯API,例如JSR 177 or the Open Mobile API,而仅仅在NfcExecutionEnvironment (NFC-EE)类中提供了一个非常基础的通讯接口,它只有三个公共方法。

publicclass NfcExecutionEnvironment {

public void open() throws IOException {...}

public void close() throws IOException{...}

public byte[] transceive(byte[] in) throwsIOException {...}

}

通过这个简单的接口就足以与SE通讯了,我们现在需要实例化一个访问接口。通过NfcAdapterExtras类的一个静态方法,可以完成对卡模拟流程(目前仅支持内置SE,因为缺少UICC接口方法)和NFC-EE的管理。向SE发送一条命令的完整代码如下:

NfcAdapterExtrasadapterExtras = NfcAdapterExtras.get( NfcAdapter.getDefaultAdapter(context) );

NfcExecutionEnvironmentnfceEe = adapterExtras.getEmbeddedExecutionEnvironment();

nfcEe.open();

byte[]response = nfcEe.transceive(command);

nfcEe.close();

然而,正如我们上面讲到的,com.android.nfc_extras是一个可选库,不是SDK中的一部分。我们不能直接的引入它,因此我们要么在Android源代码中编译(将其放在/packages/apps/目录下),或者使用反射机制。由于SE接口很小,为了便于编译和测试,我们选择反射方式。获取,打开和使用NFC-EE实例的代码就变成了下面的形式:

ClassnfcExtrasClazz =Class.forName("com.android.nfc_extras.NfcAdapterExtras");

MethodgetMethod = nfcExtrasClazz .getMethod("get",Class.forName("android.nfc.NfcAdapter"));

NfcAdapteradapter = NfcAdapter.getDefaultAdapter(context);

ObjectnfcExtras = getMethod .invoke(nfcExtrasClazz, adapter);

MethodgetEEMethod = nfcExtras.getClass().getMethod("getEmbeddedExecutionEnvironment",

(Class[]) null);

Object ee= getEEMethod.invoke(nfcExtras , (Object[]) null);

ClasseeClazz = se.getClass();

MethodopenMethod = eeClazz.getMethod("open", (Class[]) null);

MethodtransceiveMethod = ee.getClass().getMethod("transceive",

new Class[] { byte[].class});

MethodcloseMethod = eeClazz.getMethod("close", (Class[]) null);

openMethod.invoke(se,(Object[]) null);

Objectresponse = transceiveMethod.invoke(se, command);

closeMethod.invoke(se,(Object[]) null);

当然我们可以更优雅的方式将其封装在一个单独的类中,正如在测试程序中使用的方式。现在我们拥有了一个与SE的有效连接,可以发送一些测试数据了。

3,  打开SE,发送命令,最后关闭SE。

我们先发送一个空的选择命令

SEConnectionsec = new SEConnection(self);

booleanconnStatus = sec.connect();

byte[]cmd = {00, (byte)0xA4, 04, 00, 00};

byte[]response = sec.sendApdu(cmd);

Stringinfo = ByteTool.ByteArrToHexString(response, 0, response.length);

Log.d(TAG,String.format("Select result: %s", info));

infoTextView.setText(info);

sec.disconnect();

需要注意的是一定要记得在最后调用close(),因为当NFC-EE被打开后,会阻塞非接触通讯界面。

4,  编译应用程序,提取签名证书并修改手机上的/etc/nfcee_access.xml

4.1,测试中使用debug方式编译应用程序,因此需要找到debug签名用的keystore,文件的路径可以从Window-->Preferences-->Android-->Build中得到

4.2,使用JRE下的keytool导出android程序debug版证书到一个文件

C:\Program Files\Java\jre6\bin>keytool -exportcert -v-keystore debug.keystore -alias androiddebugkey -storepass android >>a.bin

注意我们要的是文本编码的签名证书,而不是二进制的,因此需要将a.bin转换为HEX字符串形式。

4.3,将HEX字符串拷贝到nfcee_access.xml 中,内容如下:

<signerandroid:signature="308203……20712F" >

<package android:name="com.example.embeddedseaccess"/>

</signer>

4.4,将文件上传到手机上SD卡中,并用root权限的文件管理器覆盖/etc下的文件(原文件先备份,或在原文件上添加,否则原有的使用SE的应用可能会失效)

4.5,重启手机以使新文件生效

5,  运行访问SE的测试程序

由于SE符合GlobalPlatform(GP)标准,因此可以通过一个空选择APDU命令得到卡和主安全域Issuer Security Domain (ISD)的相关信息。该APDU指令数据为00 A4 0400 00,下面为发送的例子。

APP发送:

00A4040000

SE返回:

6F658408....9000

这是一个TLV格式的HEX数据,结尾处的9000表明指令成功。具体内容可以根据格式自行解析。如果你的手机上装有SE上的应用,例如谷歌钱包,或其它移动支付应用,并知道其AID,就可以通过APDU选择访问。

既然我们可以访问SE了,那么能否实现

1,  控制其工作模式,使SE工作在卡模拟模式

2,  在SE中添加应用以实现自己的卡模拟

本文的第二部分将讨论这些内容。

在Android中访问内置SE和基于SE的卡模拟(一)的更多相关文章

  1. 探讨Android中的内置浏览器和Chrome

    1.Android默认浏览器和Chrome的区别 Android出厂自带的浏览器:安卓WebKit浏览器,也成内置浏览器或者默认浏览器. 安卓WebKit不是Chrome.Chrome浏览器在它的用户 ...

  2. 在Eclipse+ADT中开发Android系统的内置应用

    转自:  http://www.iteye.com/topic/1050439 在Eclipse+ADT中开发Android系统的内置应用 Android系统内置有:Browser(浏览器).Mms( ...

  3. android webview 添加内置对象

    package com.android.EBrowser; import android.app.Activity;import android.graphics.Rect;import androi ...

  4. C#使用Word中的内置对话框实例

    本文实例讲述了C#使用Word中的内置对话框的方法,分享给大家供大家参考.具体实现方法如下: 使用 Microsoft Office Word 时,有时需要显示用户输入对话框.虽然可以创建自己的对话框 ...

  5. JS中的内置对象简介与简单的属性方法

    JS中的数组: 1.数组的概念: 数组是在内存中连续存储的多个有序元素的结构,元素的顺序称为下标,通过下标查找对应元素 2.数组的声明: ①通过字面量声明var arr1 = [,,,,] JS中同一 ...

  6. 秒懂ASP.NET中的内置对象

    上篇博客,小编主要简单的介绍了一下ASP.NET中的控件,这篇博客,小编主要简单总结一下ASP.NET中的内置对象,七个内置对象分别是:Request.Response.Application.Coo ...

  7. JSP中的内置对象和Struts中的Web资源的详解

    JSP中的内置对象有如下几种: request :继承于HttpServletRequest, HttpServletRequest继承ServletRequest, 获得的Request对象的方法: ...

  8. IT兄弟连 JavaWeb教程 EL表达式中的内置对象

    EL语言定义了11个隐含对象,它们都是java.util.Map类型,网页制作者可通过它们来便捷地访问Web应用中的特定数据.表1对这11个隐含对象做了说明. 1  EL表达式中的内置对象 这11个隐 ...

  9. oop(面向对象)中的内置函数

    oop中的内置函数 ​ 类中存在一些名字带有双下划线__开头的内置函数, 这些函数会在某些时候被自动调用,例如之前学习的迭代器__init__函数 一.isinstance(obj, cls) 检查o ...

随机推荐

  1. DJANGO:从当前用户的所属用户组里查找其所拥有的权限矩阵

    没办法,随时项目越来越精进,要求也越来越多. 以前的权限精度已满足不了现在的要求, 那就设计一个权限矩阵,用HOOK返回来判断吧... [莫名其妙的ORM,留个念想] 主要是在表之间的跳转,要注意语法 ...

  2. 使用phpexecel类库导出数据

    公司要求做一个功能:将数据库里的数据导出,并生成excel文件. 于是百度了下,集大牛之所长,加上自己之所长,做出了整理,并分享. 目标:使用phpexcel类库生成xml文件,并下载. 步骤一:下载 ...

  3. 《sql注入攻击与防御 第2版》的总结 之 如何确定有sql注入漏洞

    看完<sql注入攻击与防御 第2版>后,发现原来自己也能黑网站了,就一个字:太爽了. 简单总结一下入侵步骤: 1.确定是否有sql注入漏洞 2.确定数据库类型 3.组合sql语句,实施渗透 ...

  4. springmvc工作流程

    Spring MVC工作流程图   图一   图二    Spring工作流程描述       1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServle ...

  5. C# 4.0 新特性之并行运算(Parallel)

    介绍C# 4.0 的新特性之并行运算 Parallel.For - for 循环的并行运算 Parallel.ForEach - foreach 循环的并行运算 Parallel.Invoke - 并 ...

  6. 【POJ】1054 The Troublesome Frog

    题目是非常经典的搜索+剪枝.题意简言之就是,青蛙需要沿着直线踩着踏点通过田地,并且踏点需要至少为3.问哪条路径青蛙踩坏的作物最多.很好的一个条件是青蛙每次移动都是等间距的.题目需要注意将其排序. #i ...

  7. poj1054The Troublesome Frog

    链接 想O(n*n)的DP  怎么想都超内存 看讨论有说hash+DP过的 实现比较繁琐 大部分直接暴力过了 直接枚举每个i j 与他们在一条线上的点 是不是给出的点 注意它必须能跳进和跳出 #inc ...

  8. Guid 的几种形式

    Guid.NewGuid().ToString()得几种格式显示 1.Guid.NewGuid().ToString("N") 结果为:       38bddf48f43c485 ...

  9. LightOJ 1220 Mysterious Bacteria 水题

    暴力就行了,找出素因子,正的最多是30,然后负的最多是31(这一点wa了一次) #include <cstdio> #include <iostream> #include & ...

  10. mysql服务启动 但端口未监听

    mysql 启动了,用 localhost 可以连接,但是用 127.0.0.1 不能连接.可能的原因是 1. mysql为了增强安全性而跳过了端口监听,查看方法: 用mysql> SHOW V ...