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

简介: 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、华为云、运营派、产品壹佰、鸟哥笔记、光环国际、商业新知、腾讯调研云等头部垂直类媒体转载。文章见仁见智,各位看官可策略性选择对于自己有用的部分。

相关文章
|
1月前
|
安全 Linux KVM
倚天产品介绍|倚天虚拟化:CPU虚拟化原理介绍
虚拟化技术中最关键的技术之一就是CPU虚拟化。在没有硬件辅助虚拟化技术出来之前,通常都是通过TCG(软件进行指令翻译)的方式实现CPU虚拟化。但是由于TCG方式的虚拟化层开销太大,性能太差,因此引入了硬件辅助虚拟化技术。
|
6月前
|
SQL Java 数据库连接
联表查询 && 索引 && 事务 && JDBC使用 &&CPU工作原理 && 线程概念 && Thread类的用法
联表查询 && 索引 && 事务 && JDBC使用 &&CPU工作原理 && 线程概念 && Thread类的用法
135 0
|
1月前
|
存储 缓存 并行计算
【软件设计师备考 专题 】CPU和存储器的组成、性能和基本工作原理
【软件设计师备考 专题 】CPU和存储器的组成、性能和基本工作原理
56 0
|
6月前
|
机器学习/深度学习 人工智能 并行计算
CPU 和 GPU 的协同工作原理
CPU 和 GPU 的协同工作原理
320 0
|
7月前
|
安全 Linux 调度
倚天虚拟化:CPU虚拟化原理介绍
虚拟化技术中最关键的技术之一就是CPU虚拟化。在没有硬件辅助虚拟化技术出来之前,通常都是通过TCG(软件进行指令翻译)的方式实现CPU虚拟化。但是由于TCG方式的虚拟化层开销太大,性能太差,因此引入了硬件辅助虚拟化技术。
|
8月前
|
人工智能 弹性计算 Cloud Native
平头哥杨平超:倚天CPU架构以及产品特性介绍
2023年8月8日,【倚天实例迁移课程】首节课程《倚天CPU架构以及产品特性介绍》正式上线,由平头哥倚天解决方案架构师杨平超主讲,内容涵盖:倚天710芯片概述;倚天710的主要特点;倚天710应用落地介绍,本期节目也在阿里云官网、阿里云微信视频号、阿里云钉钉视频号、InfoQ官网、阿里云开发者微信视频号、阿里云创新中心直播平台&微信视频号同步播出。
平头哥杨平超:倚天CPU架构以及产品特性介绍
|
算法 Linux PHP
PHP是如何和Linux的CPU交互的?生命周期是怎样的?底层原理是什么?
PHP是如何和Linux的CPU交互的?生命周期是怎样的?底层原理是什么?
|
安全 Linux
如何查看Linux服务器的CPU频率?底层原理是什么?
如何查看Linux服务器的CPU频率?底层原理是什么?
1112 0
|
PHP 开发者
PHP是如何和电脑CPU交互的?底层原理是什么?
PHP是如何和电脑CPU交互的?底层原理是什么?
|
Linux 数据中心 Docker
Linux搭建了Docker ,每个容器分配的内存和CPU是多少?是怎么分配的?底层原理是什么?
Linux搭建了Docker ,每个容器分配的内存和CPU是多少?是怎么分配的?底层原理是什么?
1223 0