【IoT】NFC CPU 卡在实际产品中的操作流程及原理

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: CPU卡

1、CPU 卡读写流程和原理解析

CPU 卡包含一个微处理器,其功能相当于一台微型计算机。

CPU 卡内集成电路中包括中央处理器(CPU)、只读存储器(ROM)、随机存储器(RAM)、电可擦除可编程只读存储器(EEPROM)等。

使用 FMCOS,由传输管理、文件管理、安全体系、命令解释四个功能模块组成。

传输管理:

监督卡与终端之间的通信,保证数据正确地传输。

文件管理:

区别于其他卡按区块存储数据的方式。CPU 卡将用户数据以文件的形式存储在 EEPROM 中,保证访问文件的快速性和安全性。

安全体系:

涉及对卡的鉴别与核实,对文件访问时的权限控制机制。

命令解释:

根据接收到的命令检查各项参数是否正确,执行相应的操作。

发卡流程:

1)选卡

2)发送 RATS 指令

3)选择文件

4)外部认证:所谓外部认证,就是检查你是否有合法的权限对 CPU 卡进行各种操作。

5)其他读写操作

CPU 卡 MF 目录下的 Key 文件里有一个主控密钥,由该密钥的属性设置来决定你是否有权限对 CPU 卡进行卡片初始化、MF 擦除以及卡结构修改等权限。

首先你需要拥有主控密钥,通常你并不知道主控密钥的内容(除非是未经初始化过的卡,初始主控密钥由卡片制造商决定,我用的卡初始密钥为全 F),主控密钥储存在 ESAM 卡中,你需要通过 SAM 卡提供的分散指令(两次通用 DES 计算),得到主控密钥 CCk。然后通过 CPU 卡提供的取随机数指令,取得 8 字节随机数 sRandom(这个随机数不仅返回给终端,而且还存储在 CPU 卡内某一位置),用 CCK 对 sRandom 做 DES 加密,得到结果 sRet。

然后使用外部认证指令将 sRet 发送到 CPU 卡,CPU 卡使用 MF 下的主控密钥对 sRet 做解密,将得到的结果与刚才送给终端的随机数比较,如果相同,则外部认证通过。

CPU 卡的安全寄存器状态置为主控密钥设置的后续状态,同时你也拥有了该后续状态值所对应的操作的权限。

如果认证不通过,错误次数减一,你可以使用另外的主控密钥继续做外部认证,成功则错误次数清 0。

注意:

错误次数不能超过主控密钥设置的允许最大错误次数(空卡默认是3次),否则将被锁卡。

擦除 MF:

MF默认创建,你只能清除MF下的所有内容,不能删除MF。

建立 MF 下的文件:

MF 的目录结构包括:

  • 密钥文件
  • 目录数据文件
  • 基本文件 EF(包括二进制文件、记录文件等)
  • 目录文件 DF

密钥文件是你在创建一个目录时首先需要建立的,密钥文件里的各种密钥控制你对该目录以及该目录下内容的操作权限。

指令:80E00000073F005001AAFFFF

文件标识:0000

文件类型:3F

文件空间:0050(关于文件空间的设置,参加:文件空间)

读权限:01

写权限:AA

密钥文件建立后,你需要向其中写入各种密钥,包括主控密钥、维护密钥等。

然后是创建目录数据文件,变长记录文件,说明MF下有哪些目录。

最后创建EF和DF

建立 3F01 目录下的文件:

通用先建立 Key 文件并向 Key 文件中装载密钥

由于 3F01 是 ADF,所以不需要创建目录数据文件

再建立电子钱包/存折文件、电子钱包交易明细记录文件等

电子钱包/存折文件、电子钱包交易明细文件都是由PBOC规定了如何来创建维护的。

其他目录的创建方法同上:

文件空间

文件头:11 字节(文件类型+文件标识符+文件主体空间大小+权限+校验等)

每个基本文件所占的 EEPROM 空间:11字节文件头+文件主体空间

定长、普通钱包和循环文件的主体空间:记录个数x记录长度(密钥文件的记录长度为数据长度+8)

每个 DF 所占的 EEPROM 空间:11 字节 DF 头 + DF 下所有文件的空间和 + DF 名称长度

安全报文传送分三种情况:

  • 线路保护:

对传输的数据附加 4 字节的 MAC 码,接收方收到后首先进行校验,只有校验正确的数据才予以接受,这样就防止了对传输数据的篡改。

  • 线路加密:

对传输的数据进行 DES 加密,这样传输的就是密文,攻击者即使获得了数据也没有意义。

  • 线路加密保护:

对传输的数据进行 DES 加密后再附加 4byte MAC 码

如何使用安全报文传送:

在建立文件时改变文件类型,使用线路保护则最高位置为1,使用线路加密则次高位置为1,使用线路加密保护则最高位和次高位都置为1.

在对文件进行读写或使用密钥时,如需采用安全报文传送,必须置CLA的后半字节为十六进制“4”

线路保护 MAC 计算:

假设创建了一个二进制文件,使用线路保护,文件标识0x16,文件空间0x20。

则向二进制文件中写数据的指令为:

04D6960024 + val + mac

其中CLA为04,因为文件使用线路保护,所以后半字节为4

INS为D6

P1为96,其中高3位为100,低5位为文件标识

P2为00,为欲写文件的偏移量

Lc为24

val为要写入到二进制文件中的数据,0x20个字节

mac就是我们需要计算的线路保护mac

而mac又是根据写指令去除mac部分计算得到的,即是根据“04D6960024 + val”计算得到的。

Mac 计算步骤:

1) 终端向CPU卡发送GET CHALLENGE指令,取得4字节随机数,后补“0x00000000”,得到的8字节结果作为MAC计算的初始值。

2) 检查“04D6960024 + val”的字节数是否为8的倍数,不是则补“0x8000…”,是则补“0x8000000000000000”,然后将得到的结果按8字节每块分成D1、D2、D3…

3) 判断密钥的长度。

使用SAM卡计算MAC:

1) 终端向CPU卡发送GET CHALLENGE指令,取得4字节随机数,后补“0x00000000”,得到的8字节结果作为MAC计算的初始值。

2) 通用DES初始化

80 1A + P1(密钥用途)+ P2(密钥版本)+ Lc + DATA(分散因子)

3) 通用DES计算

80 FA + P1 + P2(00) + Lc + DATA(8字节随机数+)+ “04D6960024 + val + 0x8000…”

这里P1的取值是关键:00001001,即05,代表有初始值的MAC计算

返回数据即为4字节MAC

线路加密计算:

具体计算方式同DES/3DES算法,这里介绍的是如何使用SAM卡加密。

1) 通用DES初始化

80 1A + P1(密钥用途)+ P2(密钥版本)+ Lc + DATA(分散因子)

2) 通用DES计算

80 FA + P1 + P2(00) + Lc + “明文”

这里P1的取值是关键:00000000,即00,代表无后续快的加密计算

返回数据即为加密得到的密文

2、实际读写过程

void main(void)
{
Clk_Init();
GPIO_Init();
RC522_Init();

if(CARD_TYPE == CPU_Card)
{

// 1、选卡

// 2、发送 RATS,用于后续数据交互 APDU 
CardReset();                                                            //进入CPU卡ADPU目录格式
if(!(Err[0] == 0x90 && Err[1] == 0x00))
    return; 

// 3、选择文件
Select_File[0] = 0x3F;
Select_File[1] = 0x01;
PcdSelectFile(Select_File);                                             //选择DF目录文件
if(!(Err[0] == 0x90 && Err[1] == 0x00))
    return

// 4、获取外部认证随机数
PcdGetChallenge(Random,8);
if(!(Err[0] == 0x90 && Err[1] == 0x00))
    return;

// 5、本地 DES 加密随机数
DES_encrypt(Random,Down_Load_Card.Auth_Key,Random_encrypt);              //先外部认证下载卡                

// 6、外部认证
PcdExAuth(0x01,Random_encrypt);
if(!(Err[0] == 0x90 && Err[1] == 0x00))
    return;

// 7、其他操作

}
while(1);
}
3、其他接口函数

u8 Pcb; //CPU卡APDU指令分组号
u8 Err[2]={0};
u8 CARD_TYPE=0; //卡片类型
u8 CARD_UID[4]={0}; //卡片ID号
u8 M1_Card_Auth_Key[6]={0x11,0x22,0x33,0x44,0x55,0x66};

//*
// 函数名 :RC522_Init
// 描述 :初始化引脚
// 入口 :无
// 出口 :无
// 返回 :无
//*
void RC522_Init(void)
{

PG_DDR |= 0x70;    //PG6,PG5,PG4输出口
PG_CR1 |= 0x70;    //PG6,PG5,PG4推挽输出
PG_CR2 |= 0x70;    //PG6,PG5,PG4输出速度最大为10MHZ
PG_ODR &= ~0x70;//PG6,PG5,PG4输出低
        
PG_DDR &= ~0x80;//PG7 输入口  SST25输出口DO
PG_CR1 |= 0x80;
PG_CR2 &= ~0x80;//关闭PG7中断

PD_DDR |= 0x20;    //PD5输出口                                PD5--RC522片选  CS
PD_CR1 |= 0x20;    //PD5推挽输出
PD_CR2 |= 0x20;    //PD5输出速度最大为10MHZ
PD_ODR &=~0x20; //PD5输出低


PcdReset();

PcdConfigISOType('A');

}

//*
// 函数名 :PcdSwitchPCB(void)
// 描述 :切换分组号
// 入口 :
// 出口 :
// 返回 :成功返回MI_OK
//*
void PcdSwitchPCB(void)
{

switch(Pcb)
{
    case 0x00:
        Pcb=0x0A;
        break;
    case 0x0A:
        Pcb=0x0B;
        break;
    case 0x0B:
        Pcb=0x0A;
        break;
}

}

//*
// 函数名 :PcdConfigISOType(u8 type)
// 描述 :设置RC522的工作方式
// 入口 :type[IN]:卡片类型
// 出口 :无
// 返回 :成功返回MI_OK
//*
s8 PcdConfigISOType(u8 type)
{

s8 status = MI_ERR;
if(type == 'A')
{
    status = MI_OK;
    ClearBitMask(Status2Reg,0x08);    //状态2寄存器
    WriteRawRC(ModeReg,0x3D);        //和Mifare卡通讯,CRC初始值0x6363
    WriteRawRC(RxSelReg,0x10);        //选择内部接收器设置,内部模拟部分调制信号,发送数据后,延时6个位时钟,接收
    WriteRawRC(RFCfgReg,0x7F);        //配置接收器,48dB最大增益  
    WriteRawRC(TReloadRegL,0x30);   //定时器的低8位数据 
    WriteRawRC(TReloadRegH,0x00);    //定时器的高8位数据,实际值是0xD3E这部分主要是设置定时器寄存器
    WriteRawRC(TModeReg,0x8D);         //实际值是0X0D3E这部分主要是设置定时器寄存器TPrescaler 分频数为0xD0        
    Delay_us(1000);
    PcdAntennaOn();
}
return status;

}

//*
// 函数名 :WriteRawRC
// 描述 :写RC522寄存器
// 入口 :Address[IN]:寄存器地址
// value:写入数值
// 出口 :无
// 返回 :无
//*
void WriteRawRC(u8 Address, u8 value)
{

u8 i, ucAddr;

RC522_SCK_L;
RC522_NSS_L;
ucAddr = ((Address<<1)&0x7E);

for(i=0;i<8;i++)                                //先发送地址
{     
    if((ucAddr&0x80)==0x80)
        RC522_MOSI_H;
    else
        RC522_MOSI_L;
    //MOSI = ((ucAddr&0x80)==0x80);
    RC522_SCK_H;
    ucAddr <<= 1;
    RC522_SCK_L;       
}

for(i=0;i<8;i++)
{    
    if((value&0x80)==0x80)
        RC522_MOSI_H;
    else
        RC522_MOSI_L;
    //MOSI = ((value&0x80)==0x80);                //再发送操作数
    RC522_SCK_H;
    value <<= 1;
    RC522_SCK_L;
}
RC522_NSS_H;
RC522_SCK_H;

}
//*
// 函数名 :ReadRawRC
// 描述 :读RC522寄存器
// 入口 :Address[IN]:寄存器地址
// 出口 :无
// 返回 :读出的值
//*
u8 ReadRawRC(u8 Address)
{

 u8 i, ucAddr;
 u8 ucResult=0;

 RC522_SCK_L;
 RC522_NSS_L;
 ucAddr = ((Address<<1)&0x7E)|0x80; 

 for(i=0;i<8;i++)
 {
    if((ucAddr&0x80)==0x80)
        RC522_MOSI_H;
    else
        RC522_MOSI_L;
    //MOSI = ((ucAddr&0x80)==0x80);
    RC522_SCK_H;
    ucAddr <<= 1;
    RC522_SCK_L;
 }

 for(i=0;i<8;i++)
 {
    RC522_SCK_H;
    ucResult <<= 1;
    if(RC522_MISO)
        ucResult |= 1;
    //ucResult|=RC522_MISO&0x01;
    RC522_SCK_L;
 }

 RC522_NSS_H;
 RC522_SCK_H;
 return ucResult;

}

//*
// 函数名 :SetBitMask
// 描述 :位置1
// 入口 :reg[IN]:寄存器地址
// mask[IN]:置位值
// 出口 :无
// 返回 :无
//*
void SetBitMask(u8 reg,u8 mask)
{

u8 tmp = 0x0;
tmp = ReadRawRC(reg);
WriteRawRC(reg,tmp | mask);  // set bit mask

}

//*
// 函数名 :ClearBitMask
// 描述 :位置0
// 入口 :reg[IN]:寄存器地址
// mask[IN]:置位值
// 出口 :无
// 返回 :无
//*
void ClearBitMask(u8 reg,u8 mask)
{

u8 tmp = 0x00;
tmp = ReadRawRC(reg);
WriteRawRC(reg, tmp & ~mask);                          // clear bit mask

}
//*
// 函数名 :PcdAntennaOff
// 描述 :关闭天线
// 入口 :无
// 出口 :无
// 返回 :无
//*
void PcdAntennaOff(void)
{

ClearBitMask(TxControlReg, 0x03);                    //禁止Tx1RFEn,Tx2RFEn

}

//*
// 函数名 :PcdAntennaOn
// 描述 :开启天线,每次启动或关闭天险发射之间应至少有1ms的间隔
// 入口 :无
// 出口 :无
// 返回 :无
//*
void PcdAntennaOn(void)
{

u8 i;
i = ReadRawRC(TxControlReg);                        //读取出发送控制寄存器
if (!(i & 0x03))                                    //如果未开启,则
{
    SetBitMask(TxControlReg, 0x03);                    //开启Tx1RFEn,Tx2RFEn
}

}

//*
// 函数名 :PcdComMF522
// 描述 :通过RC522和ISO14443卡通讯
// 入口 :Command[IN]:RC522命令字
// pDataIn[IN]:通过RC522发送到卡片的数据
// InLenByte[IN]:发送数据的字节长度
// *pOutLenBit[OUT]:返回数据的位长度
// 出口 :pDataOut[OUT]:接收到的卡片返回数据
// 返回 :无
//*
s8 PcdComMF522(u8 Command, u8 pDataIn, u8 InLenByte, u8 pDataOut, u16 *pOutLenBit)
{

s8 status = MI_ERR;
u8 irqEn   = 0x00;
u8 waitFor = 0x00;
u8 lastBits;
u8 n;
u16 i;
switch(Command)
{
   case PCD_AUTHENT:
      irqEn   = 0x12;
      waitFor = 0x10;
   break;
   
   case PCD_TRANSCEIVE:
      irqEn   = 0x77;
      waitFor = 0x30;                            // 接受到数据及命令执行完毕
   break;
   
   default:
     break;
}
WriteRawRC(ComIEnReg,irqEn|0x80);                // 容许除定时器中断请求以为得所有中断请求
ClearBitMask(ComIrqReg,0x80);                    // 屏蔽位清除
WriteRawRC(CommandReg,PCD_IDLE);                // 取消当前命令
SetBitMask(FIFOLevelReg,0x80);                    // 清除FIFO中的读写指针

for (i=0; i<InLenByte; i++)
{   
    WriteRawRC(FIFODataReg, pDataIn[i]);            //数据写入FIFO
}
WriteRawRC(CommandReg, Command);                    //写入命令,将缓冲区中的数据发送到天线,并激活自动接收器

if (Command == PCD_TRANSCEIVE)                        //如果命令为0C
{    
    SetBitMask(BitFramingReg,0x80);                  //相当于启动发送STARTSENG
}
i = 3000;                                            //根据时钟频率调整,操作M1卡最大等待时间=600,操作CPU卡最大等待时间=1200
do 
{
     n = ReadRawRC(ComIrqReg);                        //读取中断标志,检查数据返回
     i--;
}
while ((i!=0) && !(n&0x01) && !(n&waitFor));        // 定时器未超时,没有错误,0x01,0x30

ClearBitMask(BitFramingReg,0x80);                    // 相当于清除发送STARTSENG

if (i!=0)                                            // 定时时间到,i,没有递减到0
{    
     if(!(ReadRawRC(ErrorReg)&0x1B))                // 判断有无出现错误标志     Buffer溢出,位冲突,接收CRC错误,奇偶校验错误,
     {    
         status = MI_OK;                            // 初始化状态 
          if (n & irqEn & 0x01)                        // 若是PCD_TRANSCEIVE, irq = 0x77,  定时器为0中断产生,定时器为0时为错误
         {   
            status = MI_NOTAGERR;                   // 搜索不到卡
         }
         if (Command == PCD_TRANSCEIVE)                // 如果是发送接收指令
         { 
               n = ReadRawRC(FIFOLevelReg);            // 读取接收到的数据的字节数
              lastBits = ReadRawRC(ControlReg) & 0x07;// 2-0:RxLastBits,显示接收到最后一个字节的位数
            if (lastBits)                            // 若该位为0,最后整个字节有效
            {   
                *pOutLenBit = (n-1)*8 + lastBits;   //pOutLenBit记录总共收到的位数
            }
            else
            {   
                *pOutLenBit = n*8;                   //接收完整位数
            }
            if (n == 0)                                //假如没有中断产生
            {   
                n = 1;                               //n置1
            }
            if (n > MAXRLEN)                        // 一次最大能接受到的字节数
            {   
                n = MAXRLEN;                           //超出最大长度,只接受最大长度的值
            }
            for (i=0; i<n; i++)
            {   
                pDataOut[i] = ReadRawRC(FIFODataReg); //从FIFO读取数据   
            }
        }
     }
     else
     {   
         status = MI_ERR;                               //有错误
     }

}
SetBitMask(ControlReg,0x80); //停止定时器
WriteRawRC(CommandReg,PCD_IDLE); //清空指令
return status; //返回状态
}

//*
// 函数名 :PcdReset
// 描述 :复位RC522
// 入口 :无
// 出口 :无
// 返回 :成功返回MI_OK
//*
s8 PcdReset(void)
{

RC522_RST_L;
Delay_us(30);
RC522_RST_H;
Delay_us(100);
WriteRawRC(CommandReg,PCD_RESETPHASE);
Delay_us(100);
WriteRawRC(ModeReg,0x3D);                //和Mifare卡通讯,CRC初始值0x6363
WriteRawRC(TModeReg,0x8D);                 //定时器模式寄存器,定时器减值计数
WriteRawRC(TReloadRegL,0x30);            //定时器的低8位数据 
WriteRawRC(TReloadRegH,0x00);            //定时器的高8位数据
WriteRawRC(TPrescalerReg,0x3E);             //实际值是0X0D3E这部分主要是设置定时器寄存器
WriteRawRC(TxAutoReg,0x40);                 //必须要,设置逻辑1,强制100%ASK调制
Pcb=0x00;
return MI_OK;                             //定时器时间6.78M/TPrescaler(ms)

}

//*
// 函数名 :CalulateCRC
// 描述 :用MF522计算CRC16函数
// 入口 :pIndata[IN]:需要计算的数据
// len[IN]:数据长度
// 出口 :pDataOut[OUT]:输出结果的两个字节数组
// 返回 :无
//*
void CalulateCRC(u8 pIndata,u8 len,u8 pDataOut)
{

u8 i,n;
ClearBitMask(DivIrqReg,0x04);
WriteRawRC(CommandReg,PCD_IDLE);   //取消当前命令
SetBitMask(FIFOLevelReg,0x80);       //FlushBuffer 清除ErrReg 的标志位
for (i=0; i<len; i++)
    WriteRawRC(FIFODataReg, *(pIndata+i));
WriteRawRC(CommandReg, PCD_CALCCRC);
i = 0xFF;
do 
{
    n = ReadRawRC(DivIrqReg);
    i--;
}
while ((i!=0) && !(n&0x04));        //当CRCIRq 所有数据被处理完毕该位置位
pDataOut[0] = ReadRawRC(CRCResultRegL);
pDataOut[1] = ReadRawRC(CRCResultRegM);

}

//*
// 函数名 :PcdHalt
// 描述 :命令卡片进入休眠状态
// 入口 :无
// 出口 :无
// 返回 :成功返回MI_OK
//*
s8 PcdHalt(void)
{

s8 status = MI_ERR;
u16  unLen;
u8 ucComMF522Buf[MAXRLEN]; 

memset(ucComMF522Buf, 0x00, MAXRLEN);

ucComMF522Buf[0] = PICC_HALT;
ucComMF522Buf[1] = 0;
CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);

status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,4,ucComMF522Buf,&unLen);

return status;

}

//该函数只应用于复旦微电子卡,复旦微电子卡将M1卡和CPU卡两种卡集成在一张卡片内,返回卡片类型均为0x4000,只能通过卡片存储器大小区分,其他公司的卡片因使用上述函数
s8 PcdSelect(u8 *pSnr)
{

s8 status;
u8 i;
u16 unLen;
u8 ucComMF522Buf[MAXRLEN];         

ClearBitMask(Status2Reg,0x08);                    // 清空校验成功标志    

memset(ucComMF522Buf, 0x00, MAXRLEN);

ucComMF522Buf[0] = PICC_ANTICOLL1;                // 防冲突
ucComMF522Buf[1] = 0x70;                        // 发送7字节 
ucComMF522Buf[6] = 0;                            //ID校验清0
for (i=0; i<4; i++)
{
    ucComMF522Buf[i+2] = *(pSnr+i);                // 保存卡ID
    ucComMF522Buf[6]  ^= *(pSnr+i);                // 计算校验值
}
CalulateCRC(ucComMF522Buf,7,&ucComMF522Buf[7]);    // 生成发送内容的CRC校验,保存到最后两个字节
status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,9,ucComMF522Buf,&unLen);    // 发送选卡,及CRC校验

if((status != MI_OK) || (unLen != 0x18))        // 返回结果正确, 并且长度为24位, 3字节,(1)卡内存储器大小+(2)CRC
{                                               
    status = MI_ERR;                            // 错误
}
else             
    status = ucComMF522Buf[0];                    //M1卡返回0x08  表示卡片容量是8K bits大小;CPU卡返回0x28    表示卡片容量为40K bits大小;

return status;                                    // 返回结果

}

//*
// 函数名 :PcdAnticoll
// 描述 :防冲撞
// 入口 :pSnr[OUT]:卡片序列号,4字节
// 出口 :无
// 返回 :成功返回MI_OK
//*
s8 PcdAnticoll(u8 *pSnr)
{

s8 status;
u8 i,snr_check=0;
u16  unLen;
u8 ucComMF522Buf[MAXRLEN]; 

ClearBitMask(Status2Reg,0x08);            // 清空校验成功标志    
WriteRawRC(BitFramingReg,0x00);            // 最后一个字节发送所有数据
ClearBitMask(CollReg,0x80);                // CollRegCollReg    0冲突结束后冲突位被置零

memset(ucComMF522Buf, 0x00, MAXRLEN);

ucComMF522Buf[0] = PICC_ANTICOLL1;            // 防冲突指令,所有位在接收到冲突后将清除
ucComMF522Buf[1] = 0x20;                    // 发送2个字节

status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,2,ucComMF522Buf,&unLen);

if (status == MI_OK)                        
{
     for (i=0; i<4; i++)                    // 接受卡ID
     {   
         *(pSnr+i)  = ucComMF522Buf[i];        // 0-3:ID
         snr_check ^= ucComMF522Buf[i];        // 校验ID
     }
     if (snr_check != ucComMF522Buf[i])        // 4:校验值
     {      
         status = MI_ERR;                    // 校验出错
     }
}
else
{
    status = MI_ERR;
}
SetBitMask(CollReg,0x80);                    // CollRegCollReg    在106kbps良好的防冲突情况下该位置1
return status;                                // 返回结果

}

//该函数只应用于复旦微电子卡,复旦微电子卡将M1卡和CPU卡两种卡集成在一张卡片内,返回卡片类型均为0x4000,只能通过卡片存储器大小区分,其他公司的卡片因使用上述函数
s8 PcdRequest(u8 req_code)
{

s8 status =MI_OK;  
u16 unLen;
u8 ucComMF522Buf[MAXRLEN]; 

/*清空,做准备工作*/
PcdReset();

ClearBitMask(Status2Reg,0x08);            // 清空校验成功标志,清除MFCrypto1On位
WriteRawRC(BitFramingReg,0x07);            // StartSend =0;RxAlign=0定义最后一个字节发送的位数,发送7个位
SetBitMask(TxControlReg,0x03);            // 两天线发射信号,Tx1RFEn,Tx2RFEn置1

memset(ucComMF522Buf, 0x00, MAXRLEN);

ucComMF522Buf[0] = req_code;                //寻卡方式,所有卡还是其他什么卡

status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,1,ucComMF522Buf,&unLen);// 将收到的卡片类型号保存

if(status == MI_OK)
{
    //*pTagType     = ucComMF522Buf[0];
    //*(pTagType+1) = ucComMF522Buf[1];
}     
else
{
    status = MI_ERR;
}
return status;                                //返回结果

}

//*
// 函数名 :PcdRats
// 描述 :转入APDU命令格式
// 入口 :
// 出口 :
// 返回 :成功返回MI_OK
//*
s8 PcdRats(void)
{

s8 status =MI_ERR;  
u16 unLen;
u8 ucComMF522Buf[MAXRLEN]; 

ClearBitMask(Status2Reg,0x08);    // 清空校验成功标志,清除MFCrypto1On位

memset(ucComMF522Buf, 0x00, MAXRLEN);

ucComMF522Buf[0] = 0xE0;        
ucComMF522Buf[1] = 0x51;                

CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);    // 生成发送内容的CRC校验,保存到最后两个字节

status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,4,ucComMF522Buf,&unLen);// 将收到的卡片类型号保存

if (status == MI_OK)
{
    status = MI_OK;
}     
else
    status = MI_ERR;     
return status;                                //返回结果

}

//*
// 函数名 :PcdGetChallenge
// 描述 :外部认证密钥
// 入口 :keyflag: 外部认证密钥标识号
// Ran_Len: 随机数长度
// 出口 :pRan: 4个或8个字节加密随机数
// 返回 :成功返回MI_OK
//*
void PcdGetChallenge(u8* pRan, u8 Ran_Len)
{

s8 status =MI_ERR;  
u16 unLen;
u8 i,ucComMF522Buf[MAXRLEN]; 

ClearBitMask(Status2Reg,0x08);        //清空校验成功标志,清除MFCrypto1On位

memset(ucComMF522Buf, 0x00, MAXRLEN);

 PcdSwitchPCB();

ucComMF522Buf[0] = Pcb;
ucComMF522Buf[1] = 0x01;
ucComMF522Buf[2] = 0x00;        
ucComMF522Buf[3] = 0x84;                
ucComMF522Buf[4] = 0x00;
ucComMF522Buf[5] = 0x00;            
ucComMF522Buf[6] = Ran_Len;            //RanLe个字节的随机数    

CalulateCRC(ucComMF522Buf,7,&ucComMF522Buf[7]);    // 生成发送内容的CRC校验,保存到最后两个字节

status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,9,ucComMF522Buf,&unLen);// 将收到的卡片类型号保存     

if (status == MI_OK)
{
    Err[0] = ucComMF522Buf[Ran_Len+2];
    Err[1] = ucComMF522Buf[Ran_Len+3]; 
    if(Err[0] == 0x90 && Err[1] == 0x00)
    {
        for(i = 0;i<Ran_Len;i++)
            *(pRan+i) = ucComMF522Buf[2+i];
    }
}
else
{
    Err[0] = 0xFF;
    Err[1] = 0xFF; 
}

}
//*
// 函数名 :PcdExAuth
// 描述 :外部认证密钥
// 入口 :keyflag: 外部认证密钥标识号
// pRan: 8个字节加密随机数.
// 出口 :
// 返回 :成功返回MI_OK
//*
void PcdExAuth(u8 keysign, u8 *pRan)
{

s8 status =MI_ERR;  
u16 unLen;
u8 ucComMF522Buf[MAXRLEN]; 

ClearBitMask(Status2Reg,0x08);            // 清空校验成功标志,清除MFCrypto1On位

memset(ucComMF522Buf, 0x00, MAXRLEN);

 PcdSwitchPCB();

ucComMF522Buf[0] = Pcb;    
ucComMF522Buf[1] = 0x01;
ucComMF522Buf[2] = 0x00;    
ucComMF522Buf[3] = 0x82;                
ucComMF522Buf[4] = 0x00;
ucComMF522Buf[5] = keysign;                //认证的密码标识号
ucComMF522Buf[6] = 0x08;
ucComMF522Buf[7] = pRan[0];                //8个字节的随机数
ucComMF522Buf[8] = pRan[1];
ucComMF522Buf[9] = pRan[2];
ucComMF522Buf[10] = pRan[3];
ucComMF522Buf[11] = pRan[4];
ucComMF522Buf[12] = pRan[5];
ucComMF522Buf[13] = pRan[6];
ucComMF522Buf[14] = pRan[7];

CalulateCRC(ucComMF522Buf,15,&ucComMF522Buf[15]);    // 生成发送内容的CRC校验,保存到最后两个字节
                                    
status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,17,ucComMF522Buf,&unLen);// 将收到的卡片类型号保存

if (status == MI_OK)
{
    Err[0] = ucComMF522Buf[2];
    Err[1] = ucComMF522Buf[3]; 
}
else
{ 
     Err[0] = 0xFF;
    Err[1] = 0xFF; 
}

}
//*
// 函数名 :PcdSelectFile
// 描述 :选择文件
// 入口 :ChooseType : 选择方式
// Lc : 根据选择方式而定数据长度
// pDataIn : 文件标识符或者DF 名称
// 出口 :
// 返回 :成功返回MI_OK
//*
void PcdSelectFile(u8* pDataIn)
{

s8 status =MI_ERR;  
u16 unLen;
u8 ucComMF522Buf[MAXRLEN]; 

ClearBitMask(Status2Reg,0x08);                    // 清空校验成功标志,清除MFCrypto1On位

memset(ucComMF522Buf, 0x00, MAXRLEN);

 PcdSwitchPCB();

ucComMF522Buf[0] = Pcb;
ucComMF522Buf[1] = 0x01;
ucComMF522Buf[2] = 0x00;
ucComMF522Buf[3] = 0xA4;                
ucComMF522Buf[4] = 0x00;                            //计算种类
ucComMF522Buf[5] = 0x00;                            //认证的密码标识号
ucComMF522Buf[6] = 0x02;                                    
ucComMF522Buf[7] = pDataIn[0];                        //写入内容
ucComMF522Buf[8] = pDataIn[1];                                    
CalulateCRC(ucComMF522Buf,9,&ucComMF522Buf[9]);    // 生成发送内容的CRC校验,保存到最后两个字节

status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,11,ucComMF522Buf,&unLen);// 将收到的卡片类型号保存        

//ReaderSend(ucComMF522Buf, MAXRLEN);
                
if (status == MI_OK)
{
    Err[0] = ucComMF522Buf[unLen/8-4];
    Err[1] = ucComMF522Buf[unLen/8-3];
}
else
{
    Err[0] = 0xFF;
    Err[1] = 0xFF; 
}

}

//*
// 函数名 :GetCard()
// 描述 :IC卡检测,并返回数据
// 入口 :req_code[IN]:寻卡方式, 0x52 = 寻感应区内所有符合14443A标准的卡,0x26 = 寻未进入休眠状态的卡
// 出口 :pTagType[OUT]:卡片类型代码
// 针对复旦微电子卡片,以卡片存储器容量来区分
// 0x08 = M1卡 表示卡片容量是8K bits大小;
// 0x28 = CPU卡 表示卡片容量为40K bits大小;
// snr[OUT]:IC卡ID
// 返回 :成功返回MI_OK
//*
s8 GetCard(u8 Reqcode, u8* pSnr)
{

s8 status = MI_OK;
status = PcdRequest(Reqcode);                              //寻卡

status = PcdAnticoll(pSnr);                                //防冲撞, 获取ID

status = PcdSelect(pSnr);                                    //选择卡片
if(status == 0x28 || status == 0x08)
    CARD_TYPE = status;

return status;

}

//*
// 函数名 :CpuReset()
// 描述 :CPU卡专用复位
// 入口 :
// 出口 :
// 返回 :成功返回MI_OK
//*
void CardReset(void)
{
s8 status = MI_OK;

status = PcdRats();
if(status)
{

  Err[0] = 0xFF;
  Err[1] = 0xFF;
  return;

}

Err[0] = 0x90;
Err[1] = 0x00;
}

/
//功 能:验证卡片密码
//参数说明: auth_mode[IN]: 密码验证模式
// 0x60 = 验证A密钥
// 0x61 = 验证B密钥
// addr[IN]:块地址
// pKey[IN]:密码
// pSnr[IN]:卡片序列号,4字节
//返 回: 成功返回MI_OK
/
s8 PcdAuthState(u8 auth_mode,u8 addr,u8 pKey,u8 pSnr)
{

s8 status = MI_OK;
u16 unLen;
u8 i,ucComMF522Buf[MAXRLEN];

ucComMF522Buf[0]=auth_mode;//装密钥模式A或B
ucComMF522Buf[1]=addr;//装块地址
for(i=0;i<6;i++)
{   
    ucComMF522Buf[i+2] = *(pKey+i);   //装密码
} 
for(i=0;i<6;i++)
{   
    ucComMF522Buf[i+8] = *(pSnr+i);   //装卡片序列号
}

status=PcdComMF522(PCD_AUTHENT,ucComMF522Buf,12,ucComMF522Buf,&unLen);
if((status!=MI_OK)|| (!(ReadRawRC(Status2Reg) & 0x08)))
{   
    status= MI_ERR;   
}

return status;

}

/
//功 能:读取M1卡一块数据
//参数说明: addr[IN]:块地址
// pData[OUT]:读出的数据,16字节
//返 回: 成功返回MI_OK
/
s8 PcdRead(u8 addr,u8 *pData)
{

s8 status = MI_OK;
u16  unLen;
u8 i,ucComMF522Buf[MAXRLEN]; 

ucComMF522Buf[0]= PICC_READ;//读块命令
ucComMF522Buf[1]= addr;//装块地址
CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);//CRC校验

status= PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,4,ucComMF522Buf,&unLen);
if((status == MI_OK) && (unLen == 0x90))
{
    for (i=0; i<16; i++)
    {    
        *(pData+i) = ucComMF522Buf[i];   //读取块中的数据
    }
}
else
{   
    status = MI_ERR;   
}

return status;

}

/
//功 能:读取M1卡一块数据
//参数说明: Sector[IN]:块地址
// pData[OUT]:读出的数据,16字节
//返 回: 成功返回MI_OK
/
void M1_Card_Read_Data(u8 Sector, u8 *pData)
{

s8 status=0;

status = PcdAuthState(PICC_AUTHENT1A,Sector,M1_Card_Auth_Key,CARD_UID);
if(status == MI_ERR)
    return;

status = PcdRead(Sector,pData);
if(status == MI_ERR)
    return;

}

/
//功 能:写数据到M1卡一块
//参数说明: addr[IN]:块地址
// pData[IN]:写入的数据,16字节
//返 回: 成功返回MI_OK
/
s8 PcdWrite(u8 addr,u8 *pData)
{

s8 status;
u16  unLen;
u8 i,ucComMF522Buf[MAXRLEN]; 

ucComMF522Buf[0] = PICC_WRITE;//写块命令
ucComMF522Buf[1] = addr; //块地址
CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);//CRC校验

status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,4,ucComMF522Buf,&unLen);

if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{   
    status = MI_ERR;   
}
    
if (status == MI_OK)
{
    for (i=0; i<16; i++)
    {   
        ucComMF522Buf[i] = *(pData+i);   //装入要写入的数据
    }
    CalulateCRC(ucComMF522Buf,16,&ucComMF522Buf[16]);//CRC校验

    status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,18,ucComMF522Buf,&unLen);
    if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
    {   
        status = MI_ERR;   
    }
}

return status;

卫朋

人人都是产品经理受邀专栏作家,CSDN 嵌入式领域新星创作者、资深技术博主。2020 年 8 月开始写产品相关内容,截至目前,人人都是产品经理单渠道阅读 56 万+,鸟哥笔记单渠道阅读200 万+,CSDN 单渠道阅读 210 万+,51CTO单渠道阅读 180 万+。

卫朋入围2021/2022年人人都是产品经理平台年度作者,光环国际学习社区首批原创者、知识合作伙伴,商业新知 2021 年度产品十佳创作者,腾讯调研云2022年达人榜第三名。

文章被人人都是产品经理、CSDN、华为云、运营派、产品壹佰、鸟哥笔记、光环国际、商业新知、腾讯调研云等头部垂直类媒体转载。文章见仁见智,各位看官可策略性选择对于自己有用的部分。

相关文章
|
6月前
|
安全 Linux KVM
倚天产品介绍|倚天虚拟化:CPU虚拟化原理介绍
虚拟化技术中最关键的技术之一就是CPU虚拟化。在没有硬件辅助虚拟化技术出来之前,通常都是通过TCG(软件进行指令翻译)的方式实现CPU虚拟化。但是由于TCG方式的虚拟化层开销太大,性能太差,因此引入了硬件辅助虚拟化技术。
|
SQL Java 数据库连接
联表查询 && 索引 && 事务 && JDBC使用 &&CPU工作原理 && 线程概念 && Thread类的用法
联表查询 && 索引 && 事务 && JDBC使用 &&CPU工作原理 && 线程概念 && Thread类的用法
159 0
|
1月前
|
存储 缓存
CPU运算器的工作原理基于其内部结构,通过执行算术和逻辑操作来完成各种任务
CPU运算器的工作原理基于其内部结构,通过执行算术和逻辑操作来完成各种任务
55 3
|
1月前
CPU的工作原理基于其内部结构,通过执行指令来完成各种任务
CPU的工作原理基于其内部结构,通过执行指令来完成各种任务
55 2
|
1月前
CPU的原理
CPU的原理
62 1
|
3月前
|
存储 监控 Java
实时计算 Flink版产品使用问题之随着时间增加,作业的CPU繁忙度增加,是什么原因
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
4月前
|
运维 DataWorks 安全
DataWorks产品使用合集之如何查看空间资源、CPU、内存和存储空间容量
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
4月前
|
存储 运维 Serverless
函数计算产品使用问题之如何规避因提高CPU规格而导致的内存规格不必要增加的问题
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
4月前
|
机器学习/深度学习 分布式计算 大数据
MaxCompute产品使用合集之如何查看空间资源、CPU和内存以及存储空间容量
MaxCompute作为一款全面的大数据处理平台,广泛应用于各类大数据分析、数据挖掘、BI及机器学习场景。掌握其核心功能、熟练操作流程、遵循最佳实践,可以帮助用户高效、安全地管理和利用海量数据。以下是一个关于MaxCompute产品使用的合集,涵盖了其核心功能、应用场景、操作流程以及最佳实践等内容。
|
5月前
|
运维 Serverless Nacos
Serverless 应用引擎产品使用合集之在访问量过大的情况下,函数配置的cpu和内存会自动扩容吗
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。