射频识别(RFID)技术

实验:证实

作者:陈广 日期:2019-2-19


实验目的

对标签数据区进行读写加减等操作前,必须对电子标签进行证实操作。本实验讲述 Mifare 标签的直接密钥证实操作以及借助读写器 EEProm 进行证实操作,帮助大家掌握 HR8002 读写器的各种证实操作,以及理解不同证实操作的应用场景。

直接密码证实

我做了一个 Demo(源码会随同 HBLib 库一起放在 GitHub),让大家可以更改第 15 号扇区密码,以方便验证程序。打开 Demo 程序,选择菜单项【实验】➤【ISO14443A】➤【证实】打开此程序,如下图所示:

图 1:上位机 Demo

密钥操作需十分谨慎,密码A是永远无法读取的,忘记了操作的扇区就作废了。更改密码的操作最好是在模拟器上进行。

单击 Demo 的【选卡】按钮,成功选卡后,再单击【读取15号扇区控制块数据】,我们可以在消息框内看到返回的扇区数据结果为:

00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF

密码 A 部分永远显示为 0。

密码 A 证实

新建一个控制台应用程序,输入如下代码:

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 号扇区进行密码验证。

思考题(难度:*):更改以上程序,使用户可以选择输入指定扇区号进行验证。

密码 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);
    //输入密码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就可以了。

使用读写器 EEPROM 中的密钥进行验证

HR8002 读写器支持密码存储功能。它允许我们将密码携带一个扇区号写入读写器内部 EEPROM。在进行标签的密码证实时,可以直接使用 EEPROM 内的密码与标签上的密码进行比对以完成证实操作。

打开上位机 Demo 程序,单击【选卡】按钮,成功后在【新密码】编辑框输入123456,然后单击【更改密码B】按钮(操作密码A实在太危险,这里就用密码B吧)。更改成功后效果如下图所示:

图 1:更改 15 号扇区密码 B

将密码存入 EEPROM

接下来我们编写程序,将密码存入 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。

使用 EEPROM 内的密码进行验证

接下来将之前写的验证密码 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 内还是比较安全的。使用场景如公交车以及地铁站刷卡,你不可能每次让用户使用时输入密码,而且用户根本就不知道密码。使用这种方式存储密码相对就比较安全了。刷卡机密码设置好后,只有你发出去的卡才能使用。

;

© 2018 - IOT小分队文章发布系统 v0.3