本文针对NT3H2111芯片DataSheet中有关Authenticate的内容进行整理汇总,用一种比较好理解的方式叙述
DataSheet下载链接:
https://www.nxp.com/docs/en/data-sheet/NT3H2111_2211.pdf
NFC技术的一个重要应用场景是,移动支付领域,因此相关的安全技术是必不可少的
对于这种安全认证技术,NXP公司的这款NT3H2111芯片,自然有很多优化的措施,本文介绍NXP这款产品是如何在芯片的层面完成密码认证的机制的
下图是NFC角度的NT3H2111芯片的memory组织方式
可以看到,从NFC的角度,芯片的第一层框架为sector,主要的东西都放在第一个sector里面;第二层框架是page,每一个sector有255个page,;第三层框架是Byte,也就是字节,一个Page有4个字节
下面描述一下整个认证的过程:
首先,一开始的时候密码保护状态肯定是禁用的,这个时候请关注一下AUTH0这个位置,其实本质上AUTH0就是一个位于Sector0并且Page地址在E3h的第4个Byte(从0开始数是第4个)的那个Byte
AUTH0的作用是,指定在SECTOR0中,被保护的区域是从哪里开始的,也就是被保护区域的起始地址。当然若AUTH0里的值大于EBh,则密码保护会被禁用
关于AUTH0,有一句话我还没看明白,先挖个坑
从NFC的视角来看,有一块区域是专门用于实现这个密码认证的,如图所示,从E3H到E7H
如果从I2C的角度来看,是block从38H到39H
当然这款区域是在EEPROM里的(SRAM相当于内存,掉电就没了,显然这些重要的东西不应该放在SRAM里面)
我们已经知道,这款NFC芯片的memory(不知道应该怎么翻译)根据存储体性质的不同,可以分为2大块,即EEPROM和SRAM,那么,从某种意义上,我们可以把这个芯片作如下划分:
- SRAM
- 受保护的EEPROM(就是上面提到的)
- 不受保护的EEPROM
当然受保护的EEPROM容量会小一些,不受保护的EEPROM容量大一些
下面就开始说存放密码的区域了,也就是PWD区域
- PWD,用于存放32位的密码
- PACK,用于存放16位的Ack
显然这两个区域你是不可能通过手机去读的,DataSheet里面也写的很清楚
就是说你要是去读取这两个区域,读取的结果只能是返回全0。当然写是可以的,也就是刚刚上面提到的
PT_I2C
这其实是一个Byte的简称,也就是那个位于SECTOR0的Page地址为E7H的第0个Byte,我们给他取名字叫做PT_I2C
PT_I2C里面的8位是这样的
这里主要关注一下SRAM_PORT这一位(如果我们要给某一bit取一个别名,就在名字后面加上_PORT这样,显然,SRAM_PORT就是和SRAM的控制相关的一个bit)
- 0 不需要pass through模式的密码认证
- 1 需要pass through模式的密码认证
当然了,从I2C角度来说,我们始终有这样的权限去读写:
PWD_AUTH
假设你现在手上有一个带有NFC功能的手机,接下来是如何实现认证的呢?
这里需要介绍一条非常重要的指令,也就是PWD_AUTH,如图所示
这条指令带一个参数,也就是你的输入密码
这样看太抽象了,我们不如看一下源代码,这是在发送指令
有意思的是,这个函数在整个Project中只用了一次,就是用在这个地方
authenticatePlus这个函数相当于在pwdAuth的基础上再封装了一层
那么哪些地方用到了authenticatePlus呢
很有意思的事情,我要去认证,我当然要先判断我自己现在的权限状态,那么如何去看自己的状态呢,居然是用一个static的变量,因此我对这个变量能否及时更新有所质疑
Android客户端中所有对于mAuthStatus的更新,都是通过这个函数获得的。mAuthStatus我个人认为你应该将他理解成为NXP芯片中的某个变量的影射,而不是一个Android客户端中的一个static变量!
显然这个的具体实现就是这个了,先挖坑,以后再说
@Override
public int getProtectionPlus() {
try {
reader.SectorSelect((byte) 0);
byte[] auth0 = getAuth0Register();
if(auth0 != null && auth0.length < 4) {
try {
readSRAMBlock();
return AuthStatus.Protected_RW.getValue();
} catch (IOException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
}
return AuthStatus.Protected_RW_SRAM.getValue();
} else {
if((auth0[3] & 0xFF) <= 0xEB) {
byte[] access = getAccessRegister();
byte[] pti2c = getPTI2CRegister();
if (((0x0000080 & access[0]) >> Access_Offset.NFC_PROT.getValue() == 1) &&
((0x0000004 & pti2c[0]) >> PT_I2C_Offset.SRAM_PROT.getValue() == 1)) {
return AuthStatus.Protected_RW_SRAM.getValue();
} else if (((0x0000080 & access[0]) >> Access_Offset.NFC_PROT.getValue() == 1)
&& ((0x0000004 & pti2c[0]) >> PT_I2C_Offset.SRAM_PROT.getValue() == 0)) {
return AuthStatus.Protected_RW.getValue();
} else if (((0x0000080 & access[0]) >> Access_Offset.NFC_PROT.getValue() == 0)
&& ((0x0000004 & pti2c[0]) >> PT_I2C_Offset.SRAM_PROT.getValue() == 1)) {
return AuthStatus.Protected_W_SRAM.getValue();
} else if (((0x0000080 & access[0]) >> Access_Offset.NFC_PROT.getValue() == 0)
&& ((0x0000004 & pti2c[0]) >> PT_I2C_Offset.SRAM_PROT.getValue() == 0)) {
return AuthStatus.Protected_W.getValue();
}
}
}
return AuthStatus.Unprotected.getValue();
} catch (IOException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
} catch (CommandNotSupportedException e) {
e.printStackTrace();
}
// Check if the SRAM is lock
try {
readSRAMBlock();
return AuthStatus.Protected_RW.getValue();
} catch (IOException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
}
return AuthStatus.Protected_RW_SRAM.getValue();
}
AUTHLIM
当然,为了防止暴力破解,用AuthLIM来表示最多可以尝试的次数,当开启后,他会自动记录下你的输入错误密码的次数,这个很简单,不多说了