NFC 浅析(一)
2015-06-11 19:40
393 查看
一.编写目的
NFC作为一项新技术,已经逐渐的开始应用于各个领域了。但是国内由于没有太多这种基础设施与条件,所以应用不是太广泛,但这也是一个趋势了。就目前网络上关于NFC的资料比较缺乏,我这儿将我理解的东西总结下,希望对各位有用。二.ic卡
本文主要讲的是nfc识别并读写ic卡,所以在讲主菜前,有必要普及下ic卡的知识及部分原理,为后面nfc的讲解预热下。2.1 IC卡简介
Ic卡分为接触式和非接式,NFC近场通信技术是由非接触式射频识别(RFID)及互联互通技术整合演变而来,使用的是非接触式IC卡,一般就是那种芯片不露到外面的ic卡,像公交卡,食堂卡那种的。2.2 IC卡的存储规则
MF1卡分为16个扇区,每区有4块(块0~块3),共64块,按块号编址为0~63。第0扇区的块0(即绝对地址块0)用于存放芯片商,卡商相关代码,已经固化不可更改。其他各扇区的块0,块1,块2为数据块,用于存贮用户数据;块3为各扇区控制块,用于存放密码A(6字节),存取控制条件设置(4字节),密码B(6字节)。各区控制块结构相同。接下来本文中的控制指令为ff078069(一般为厂家初始指令),该控制指令在验证密码A或密码B成功后,允许读写0-2块数据。验证密码A后,能重写整个块3,但是只能读取控制指令和密码B。三. NFC的使用
3.1 NFC使用包简介
在Android NFC 应用中,Android手机通常是作为通信中的发起者,也就是作为各种NFC卡的读写器。Android对NFC的支持主要在 android.nfc 和android.nfc.tech 两个包中。android.nfc 包中主要类如下:NfcManager 可以用来管理Android设备中指出的所有NFCAdapter,但由于大部分Android设备只支持一个NFC Adapter,所以一般直接调用getDefaultAapater来获取手机中的Adapter。NfcAdapter相当于一个NFC适配器,类似于电脑装了网络适配器才能上网,手机装了NfcAdapter才能发起NFC通信。所以也可以通过获取到的adapter是否为空来判断手机是否支持NFC。
Tag 代表一个被动式Tag对象,可以代表一个标签,卡片等。当Android设备检测到一个Tag时,会创建一个Tag对象,将其放在Intent对象,然后发送到相应的Activity。
3.2 在AndroidManifest.xml中注册接受NFC数据
3.2.1 添加NFC权限
<uses-permission android:name="android.permission.NFC" />
3.2.2 指定的Activity中添加接受数据格式
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testnfc" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" /> <uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc" android:required="true" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.testnfc.MainActivity" android:label="@string/app_name" android:launchMode="singleTop" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <data android:host="*" android:pathPrefix="" android:scheme="http" /> </intent-filter> <intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED" /> </intent-filter> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" /> <intent-filter> <action android:name="android.nfc.action.TAG_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest>
其中res/xml/nfc_tech_filter.xml:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.NfcA</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcB</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcF</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NfcV</tech> </tech-list> <tech-list> <tech>android.nfc.tech.Ndef</tech> </tech-list> <tech-list> <tech>android.nfc.tech.NdefFormatable</tech> </tech-list> <tech-list> <tech>android.nfc.tech.IsoDep</tech> </tech-list> <tech-list> <tech>android.nfc.tech.MifareClassic</tech> </tech-list> <tech-list> <tech>android.nfc.tech.MifareUltralight</tech> </tech-list> </resources>
3.2.3 判断NFC
protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); nfcAdapter = NfcAdapter.getDefaultAdapter(this); pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); if (nfcAdapter == null) { Toast.makeText(this, "设备不支持NFC!", Toast.LENGTH_SHORT).show(); finish(); return; } if (!nfcAdapter.isEnabled()) { Toast.makeText(this, "请在系统设置中先启用NFC功能!", Toast.LENGTH_SHORT).show(); startActivity(new Intent( android.provider.Settings.ACTION_NFC_SETTINGS)); return; } }
3.2.4 处理接受到的tag
@Override protected void onResume() { super.onResume(); if (nfcAdapter != null) nfcAdapter.enableForegroundDispatch(this, pendingIntent, FILTERS, TECHLISTS); // 得到是否检测到ACTION_TECH_DISCOVERED触发 if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) { // 处理该intent processIntent(getIntent()); } }
private void processIntent(Intent intent) { // 取出封装在intent中的TAG Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); if (tagFromIntent == null) { return; } for (String tech : tagFromIntent.getTechList()) { System.out.println(tech); } // 获取卡id byte[] id = tagFromIntent.getId(); System.out.println(ByteArrayToHexString(id)); // 读取TAG mifareClassic = MifareClassic.get(tagFromIntent); }
3.2.5 根据获取到的mifareClassic进行读写操作
读指定块(我这里按绝对地址编码):public String readCarCode(int block, String key) { String string = ""; boolean isTrue; try { mifareClassic.connect(); int keyblock = block / 4; if ("".equals(key)) isTrue = authenticateSectorWithKeyA(keyblock, MifareClassic.KEY_DEFAULT); else { byte[] bytes = hexStringToByte(key); byte[] keyBytes = Arrays.copyOf(bytes, 6); isTrue = authenticateSectorWithKeyA(keyblock, keyBytes); } if (isTrue) { byte[] k = readBlock(block); string = new String(k, "utf-8"); } else { string = "秘钥错误"; } } catch (Exception e) { e.printStackTrace(); string = "读取失败"; } finally { try { mifareClassic.close();; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return string; }写入:
public String wirteCarCode(String carCode, int block, String key) { boolean isTrue; String string = ""; try { if (block % 4 == 3) { string = "此块是秘钥块!"; return string; } mifareClassic.connect(); int keyblock = block / 4; if ("".equals(key)) isTrue = authenticateSectorWithKeyA(keyblock, MifareClassic.KEY_DEFAULT); else { byte[] bytes = hexStringToByte(key); byte[] keyBytes = Arrays.copyOf(bytes, 6); isTrue = authenticateSectorWithKeyA(keyblock, keyBytes); } byte[] bytes = carCode.trim().getBytes("UTF-8"); byte[] a = Arrays.copyOf(bytes, 16); mifareClassic.writeBlock(block, a); if (isTrue) { } else { string = "秘钥错误!"; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); string = "写入失败!"; } finally { try { mifareClassic.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return string; }最后讲修改秘钥,这个地方要注意,修改秘钥时,不要将控制指令写坏了,如果造成不可逆的错指令写进去,则该扇区就报废了。所以这个地方我的做法就是将整块的内容读下来,然后将读出内容的秘钥A的部分覆盖成新密码,然后重新整块写进去,以达到修改秘钥时不改变控制的目的。当然如果要篡改控制的话,原理是一样的。(前面说过我这篇文章的指令基础,是出厂时设定的,验证密码a后就可以具有完全的读写权限,换成其他指令则权限有一定差异,但写法大概差不多)
public String modifyPassword(int block, String newPassword, String key) { boolean isTrue; String string = ""; try { if (block % 4 != 3) { string = "此块不是秘钥块!"; return string; } mifareClassic.connect(); int keyblock = block / 4;// 计算扇区 if ("".equals(key)) isTrue = authenticateSectorWithKeyA(keyblock, MifareClassic.KEY_DEFAULT); else { byte[] bytes = hexStringToByte(key); byte[] keyBytes = Arrays.copyOf(bytes, 6); isTrue = authenticateSectorWithKeyA(keyblock, keyBytes); } if (isTrue) { byte[] k = readBlock(block); byte[] bytes = hexStringToByte(newPassword); byte[] a = Arrays.copyOf(bytes, 6); for (int i = 0; i < 6; i++) { k[i] = a[i]; } mifareClassic.writeBlock(block, k); } else { string = "秘钥错误!"; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); string = "写入失败!"; } finally { try { mifareClassic.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return string; }
四.总结:
这篇文章简单的介绍了NFC的读卡运用,基本能满足所有的操作了,当然如果有复杂的验证过程,比如修改了控制指令,那写法也差不多,微调下也基本能满足了。相关文章推荐
- 具有异常判断的数据库事务Sql语句
- IBM Bluemix云平台24小时应用开发挑战赛参赛作品详解
- loadrunner脚本编写http协议
- 包含min函数的栈
- 在Spring3中使用注解(@Scheduled)创建计划任务
- RFC2889转发性能测试用例设计和自动化脚本实现
- java异常处理--用代码说话
- 深度注入:
- Android Java 程序员必备开发工具
- java final keyword
- MVC 常见数据验证速查手册
- 引入sun.misc.BASE64Encoder错误处理
- Please ensure that adb is correctly located at 问题解决方法
- 使用malloc在堆上创建二维数组
- HB在线打包ipa通过application loader上传出错
- SDNU 1015.最远路径【二叉树DFS】
- TIJ英文原版书籍阅读之旅——Chapter Seven:Reusing Classes
- 关于android模拟器emulator-5554 disconnected错误解决方法
- eclipce 关联 javadoc
- 新手了解.Nat