由于产品规划的因素,想实现NRF52832单片机模拟NFC门禁卡的功能;我通过这段时间查找资料和学习,对NFC实现门禁卡需要做的工作,以及其中的难点,做如下总结。
一、NFC的协议分层、卡类型
先上一张图,这张图可以清楚看到NFC的协议结构,以及NFC支持的卡类型:

图片上标注灰色的部分是标准协议,常用的就是ISO1443A, 在之上不同的厂家会实现各自独特的通讯协议和加密验证算法。从这张图可以看到,NXP的卡的种类比较多。
二、MIFARE 1K 卡
目前主流的门禁卡还是M1卡,M1卡是由NXP公司研发的门禁卡,目前是市场上主流的门卡;它的存储空间由1K,分为16个扇区,每个扇区有64个字节,每个扇区分为4个块,每块16个字节。
如下图所示:

第0扇区的第0块,保存卡UID和厂家信息,可读不可写,其它的扇区和块一般可读可写;每个扇区的最后一块保存每个扇区单独的密钥以及访问控制字。
读卡器能够正确的从M1卡中读取数据,需要经历如下步骤:读卡器发起请求、选卡、认证、读写数据,下列的图片展示了读取数据的流程:

三
、MIFARE 1K 卡 的加密和认证
M1卡采用一种叫做Crypto1的加密算法进行加密,这是一种流加密算法,在github上搜索就能找到源码。最初设计此算法的是NXP公司,此算法不开源。
- 当 MIFARE Classic 卡接近读卡器的磁场区域时,卡片会接收到读卡器发来的寻卡指令,然后按照防冲突协议发出自己的卡号UID(寻卡过程)。
- 收到UID 后,读卡器会选择这张卡(选卡过程)。
- 接着读卡器发出对某一块的认证请求,然后就开始了一个标准的三步认证协议(认证过程)。
- 卡片产生一个随机数nT 并以明文方式发送给读卡器。
- 紧接着读卡器发出它的随机数nR和对卡片的应答aR。
- 最后卡片返回一个对读卡器的应答aT(认证完成)
选卡的过程和放冲突模式是硬件自动操作的,所以从第三步开始的协议解析、身份认证、数据加密,才是工作难点。
标签对读卡器的认证
crypto1_init(&state, (uint64_t)tc[k].key); //通过密钥初始化
crypto1_word(&state, tc[k].uid ^ tc[k].tag_challenge, 0);//输入UID和标签的随机数
crypto1_word(&state, tc[k].reader_challenge_enc, 1);//读卡器发送的随机数
rresp = prng_successor(tc[k].tag_challenge, 64);//验证标签的随机数和
rresp ^= crypto1_word (&state, 0, 0);
if(rresp == tc[k].reader_response)//验证读卡器的随机数是否和标签发送的数据数一样
printf("TAG> Reader is authentic.\n");
else
printf("TAG> Reader is NOT authentic.\n");
读卡器对标签的认证
crypto1_init(&state, tc[k].key);
crypto1_word(&state, tc[k].uid ^ tc[k].tag_challenge, 0);
rchal = crypto1_word(&state, tc[k].reader_challenge, 0);
rresp = prng_successor(tc[k].tag_challenge, 64);
rresp ^= crypto1_word (&state, 0, 0);
tresp = prng_successor(tc[k].tag_challenge, 96);
tresp ^= crypto1_word (&state, 0, 0);
if(tresp == tc[k].tag_response)//读卡器验证随机数
printf("Reader> Tag is authentic.\n");
else
printf("Reader> Tag is NOT authentic.\n");
ISO14443 crc校验算法
#define CRC_A 1
#define CRC_B 2
#define BYTE unsigned char
unsigned short UpdateCrc(unsigned char ch, unsigned short *lpwCrc)
{
ch = (ch^(unsigned char)((*lpwCrc) & 0x00FF));
ch = (ch^(ch<<4));
*lpwCrc = (*lpwCrc >> 8)^((unsigned short)ch << 8)^((unsigned short)ch<<3)^((unsigned short)ch>>4);
return(*lpwCrc);
}
void ComputeCrc(int CRCType, char *Data, int Length,BYTE *TransmitFirst, BYTE *TransmitSecond)
{
unsigned char chBlock;
unsigned short wCrc;
switch(CRCType) {
case CRC_A:
wCrc = 0x6363; // ITU-V.41
break;
case CRC_B:
wCrc = 0xFFFF; // ISO 3309
break;
default:
return;
}
do {
chBlock = *Data++;
UpdateCrc(chBlock, &wCrc);
} while (--Length);
if (CRCType == CRC_B)
wCrc = ~wCrc; // ISO 3309
*TransmitFirst = (BYTE) (wCrc & 0xFF);
*TransmitSecond = (BYTE) ((wCrc >> 8) & 0xFF);
return;
}
BYTE BuffCRC_A[] = { 0x53,0x45,0x47,0x47,0x45,0x52,0x20,0x52};
BYTE BuffCRC_B[] = { 0xC8,0xF0,0xD8,0x2E,0x7B};
unsigned short Crc;
BYTE First, Second;
int crc_main(void)
{
printf("CRC-16 reference results 3-Jun-1999\n");
printf("by Mickey Cohen - mickey@softchip.com\n\n");
printf("Crc-16 G(x) = x^16 + x^12 + x^5 + 1\n\n");
printf("CRC_A of [ ");
printf("%02X ",BuffCRC_A[i]);
ComputeCrc(CRC_A, BuffCRC_A, sizeof(BuffCRC_A), &First, &Second);
printf("] Transmitted: %02X then %02X.\n", First, Second);
return(0);
}
三、使用NRF52832模拟M1门卡遇到的问题
我在52832开源代码hal_nfc_tct上修改的,防冲突和选卡已经成功了,读卡器下发认证指令 60 00 F5 7B后, 标签回复4字节随机数+2个字节CRC,此时读卡器应该回复4字节的随机数+4字节的应答 +2字节的CRC的数据,一共10个字节,但是我只收到了读卡器的9个字节。数据流如下
reader : 60 00 F5 7B
tag: 82 a4 16 6c 85 46
reader : 1a 21 6a 40 f2 ef 52 ee 32(一共只有9个字节)
如果最后这一步CRC校验成功了的话,才能开始标签对读卡器的认证;读卡器使其他的卡测试可以正常使用,我目前卡在这里:。
目前可以排查方向如下:
1.接收的数据中第一个字节丢失
2.读卡器发送的数据有特殊的协议格式,我这边解析错误
由于这两个方向都需要知道读卡器发送的数据,但是目前的读卡器是直接买的,我是不知道读卡器发送的具体数据,目前也没有什么好的方法。