您的位置:首页 > 其它

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的读卡运用,基本能满足所有的操作了,当然如果有复杂的验证过程,比如修改了控制指令,那写法也差不多,微调下也基本能满足了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: