作者:陈广 日期:2019-2-19
对标签数据区进行读写加减等操作前,必须对电子标签进行证实操作。本实验讲述 Mifare 标签的直接密钥证实操作以及借助读写器 EEProm 进行证实操作,帮助大家掌握 HR8002 读写器的各种证实操作,以及理解不同证实操作的应用场景。
我做了一个 Demo(源码会随同 HBLib 库一起放在 GitHub),让大家可以更改第 15 号扇区密码,以方便验证程序。打开 Demo 程序,选择菜单项【实验】➤【ISO14443A】➤【证实】打开此程序,如下图所示:
密钥操作需十分谨慎,密码A是永远无法读取的,忘记了操作的扇区就作废了。更改密码的操作最好是在模拟器上进行。
单击 Demo 的【选卡】按钮,成功选卡后,再单击【读取15号扇区控制块数据】,我们可以在消息框内看到返回的扇区数据结果为:
00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF
密码 A 部分永远显示为 0。
新建一个控制台应用程序,输入如下代码:
using System;
using System.Threading.Tasks;
using System.Text;
using HBLib;
using HBLib.HR8002Reader;
using HBLib.ISO14443A;
namespace CmdDemo
{
class Program
{
static ComPort com = new ComPort("COM3", 19200, 300);
static void Main(string[] args)
{
com.Open();
AuthKey();
Console.ReadLine();
}
private static async Task AuthKey()
{
Reader reader = new Reader(0x00, com);
I14443A i14443a = new I14443A(0x00, com);
//输入密码A
Console.WriteLine("请输入密码 A(6个以内英文字符,不输入字符则使用默认密码):");
string keyStr = Console.ReadLine();
await reader.ChangeToISO14443AAsync();
//请求操作
var info = await i14443a.RequestAsync(RequestMode.AllCard);
if (info.ReturnValue != ReturnMessage.Success)
{
Console.WriteLine(info.GetStatusStr());
return;
}
//防冲突操作
var info1 = await i14443a.AnticollAsync();
if (info1.ReturnValue != ReturnMessage.Success)
{
Console.WriteLine(info1.GetStatusStr());
return;
}
//选卡操作
var info2 = await i14443a.SelectAsync(info1.UID);
if (info2.ReturnValue == ReturnMessage.Success)
{
Console.WriteLine("选中标签:" + info1.GetUIDStr());
}
else
{
Console.WriteLine("感应场内无标签");
return;
}
//将用户输入的字符串转化为字节数组
byte[] keyA = new byte[6];
if (keyStr == "")
{
keyA = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
}
else
{
byte[] b = Encoding.ASCII.GetBytes(keyStr);
if (b.Length > 6)
{
Console.WriteLine("输入的密码 A 超出长度!");
return;
}
b.CopyTo(keyA, 0);
}
//密码 A 证实
var info3 = await i14443a.AuthKeyAsync(KeyType.KeyA, 15, keyA);
Console.WriteLine("发送的字节:" + info.GetSendByteStr());
Console.WriteLine("接收的字节:" + info.GetRecvByteStr());
Console.WriteLine(info.GetStatusStr());
}
}
}
运行程序,结果如下(我这里使用的是默认密码):
请输入密码 A(6个以内英文字符,不输入字符则使用默认密码):
选中标签:47E0F62A
发送的字节:06 00 41 10 01 A2 D3
接收的字节:06 00 00 04 00 70 6C
操作成功!
本程序使用字符作为密码,在证实时将其转化为字节数组,不足 6 个字节补 0。并且,此程序只针对 15 号扇区进行密码验证。
思考题(难度:*):更改以上程序,使用户可以选择输入指定扇区号进行验证。
将代码更改如下:
static ComPort com = new ComPort("COM3", 19200, 300);
static void Main(string[] args)
{
com.Open();
AuthKey();
Console.ReadLine();
}
private static async Task AuthKey()
{
Reader reader = new Reader(0x00, com);
I14443A i14443a = new I14443A(0x00, com);
//输入密码A
Console.WriteLine("请输入密码 B(6个以内英文字符,不输入字符则使用默认密码):");
string keyStr = Console.ReadLine();
await reader.ChangeToISO14443AAsync();
//请求操作
var info = await i14443a.RequestAsync(RequestMode.AllCard);
if (info.ReturnValue != ReturnMessage.Success)
{
Console.WriteLine(info.GetStatusStr());
return;
}
//防冲突操作
var info1 = await i14443a.AnticollAsync();
if (info1.ReturnValue != ReturnMessage.Success)
{
Console.WriteLine(info1.GetStatusStr());
return;
}
//选卡操作
var info2 = await i14443a.SelectAsync(info1.UID);
if (info2.ReturnValue == ReturnMessage.Success)
{
Console.WriteLine("选中标签:" + info1.GetUIDStr());
}
else
{
Console.WriteLine("感应场内无标签");
return;
}
//将用户输入的字符串转化为字节数组
byte[] keyB = new byte[6];
if (keyStr == "")
{
keyB = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
}
else
{
byte[] b = Encoding.ASCII.GetBytes(keyStr);
if (b.Length > 6)
{
Console.WriteLine("输入的密码 B 超出长度!");
return;
}
b.CopyTo(keyB, 0);
}
//密码 B 证实
var info3 = await i14443a.AuthKeyAsync(KeyType.KeyB, 15, keyB);
Console.WriteLine("发送的字节:" + info.GetSendByteStr());
Console.WriteLine("接收的字节:" + info.GetRecvByteStr());
Console.WriteLine(info.GetStatusStr());
}
密码 B 的证实和 密码 A 证实完全一样,实际上将第一个程序的倒数第四行代码中AuthKeyAsync
方法中的KeyType.KeyA
改为KeyType.KeyB
就可以了。
HR8002 读写器支持密码存储功能。它允许我们将密码携带一个扇区号写入读写器内部 EEPROM。在进行标签的密码证实时,可以直接使用 EEPROM 内的密码与标签上的密码进行比对以完成证实操作。
打开上位机 Demo 程序,单击【选卡】按钮,成功后在【新密码】编辑框输入123456
,然后单击【更改密码B】按钮(操作密码A实在太危险,这里就用密码B吧)。更改成功后效果如下图所示:
接下来我们编写程序,将密码存入 EEPROM。新建一个控制台应用程序,输入如下代码:
using System;
using System.Text;
using System.Threading.Tasks;
using HBLib;
using HBLib.HR8002Reader;
using HBLib.ISO14443A;
namespace CmdReader
{
class Program
{
static ComPort com = new ComPort("COM3", 19200, 300);
static void Main(string[] args)
{
com.Open();
LoadKey();
Console.ReadLine();
}
private static async Task LoadKey()
{
Reader reader = new Reader(0x00, com);
I14443A i14443a = new I14443A(0x00, com);
//更改为 ISO14443A 模式
await reader.ChangeToISO14443AAsync();
//将密码存入 EEPROM
byte[] keyB = Encoding.ASCII.GetBytes("123456");
var info = await i14443a.LoadKeyAsync(KeyType.KeyB, 15, keyB); //将密码存入 EEPROM
Console.WriteLine("发送的字节:" + info.GetSendByteStr());
Console.WriteLine("接收的字节:" + info.GetRecvByteStr());
Console.WriteLine(info.GetStatusStr());
}
}
}
运行程序,成功后,结果如下:
发送的字节:0D 00 4C 10 01 0F 31 32 33 34 35 36 02 EB
接收的字节:04 00 00 52 5A
操作成功!
此时,我们已经将密码"123456"存入 EEPROM,对应的扇区为 15。
接下来将之前写的验证密码 B 的程序更改如下:
static ComPort com = new ComPort("COM3", 19200, 300);
static void Main(string[] args)
{
com.Open();
AuthKey();
Console.ReadLine();
}
private static async Task AuthKey()
{
Reader reader = new Reader(0x00, com);
I14443A i14443a = new I14443A(0x00, com);
await reader.ChangeToISO14443AAsync();
//请求操作
var info = await i14443a.RequestAsync(RequestMode.AllCard);
if (info.ReturnValue != ReturnMessage.Success)
{
Console.WriteLine(info.GetStatusStr());
return;
}
//防冲突操作
var info1 = await i14443a.AnticollAsync();
if (info1.ReturnValue != ReturnMessage.Success)
{
Console.WriteLine(info1.GetStatusStr());
return;
}
//选卡操作
var info2 = await i14443a.SelectAsync(info1.UID);
if (info2.ReturnValue == ReturnMessage.Success)
{
Console.WriteLine("选中标签:" + info1.GetUIDStr());
}
else
{
Console.WriteLine("感应场内无标签");
return;
}
//使用 EEPROM 进行密码 B 证实
var info3 = await i14443a.AuthenticationAsync(KeyType.KeyB, 15);
Console.WriteLine("发送的字节:" + info.GetSendByteStr());
Console.WriteLine("接收的字节:" + info.GetRecvByteStr());
Console.WriteLine(info.GetStatusStr());
}
为验证 EEPROM 内的数据断电后不会丢失,先给读写器断电,然后再打开。运行程序,结果如下:
选中标签:47E0F62A
发送的字节:06 00 41 10 01 A2 D3
接收的字节:06 00 00 04 00 70 6C
操作成功!
我们看到,这一次的验证不再需要输入密码,方便了很多。
由于读写器 EEPROM 内的密码是无法读取出来的,所以,密码存放在 EEPROM 内还是比较安全的。使用场景如公交车以及地铁站刷卡,你不可能每次让用户使用时输入密码,而且用户根本就不知道密码。使用这种方式存储密码相对就比较安全了。刷卡机密码设置好后,只有你发出去的卡才能使用。
;