nRF52832——唯一 ID 与加密解密
唯一 ID 概念
唯一 ID 作用
nRF52xx 微控制器提供一组 64 位的唯一 ID 号,这个唯一身份标识所提供的 ID 值对任意一个 nRF52xx 微控制器,在任何情况下都是唯一的。用户在何种情况下,都不能修改这个身份标识。按照用户不同的用法,可以以字节(8 位)为单位读取,也可以以半字(16 位)或者全字(32 位)读取。对应唯一ID 号,常见的应用场合如下面几种:
- 用来作为序列号;
- 用来作为密码,在编写闪存时,将此唯一标识与软件加解密算法结合使用,提高代码在闪 存存储器内的安全性;
- 用来激活带安全机制的自举过程;
读取唯一 ID
设备唯一 ID 保存存在寄存器 FICR 中:工厂信息配置寄存器(FICR)是在工厂预先编程的,用户不能删除。这些寄存器包含特定于芯片的信息和配置。
寄存器名称 | 偏移地址 | 描述 |
---|---|---|
DEVICE ID[0] | 0x060 | 设备 identifier |
DEVICE ID[1] | 0x064 | 设备 identitier |
因此识别芯片中的唯一 ID 的方式就是读取寄存器 DEVICEID 内的值,因为这个参数值是不能修改的,出厂的时候由厂家固化的。因此寄存器 DEVICEID 为只读寄存器,我们在串口例子基础上就行修改,因此工程结构不就行修改。读取了唯一 ID 的寄存器值后,通过串口打印输出,来进行演示。编写程序如下所示:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "app_uart.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "nrf.h"
#include "bsp.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif
//#define ENABLE_LOOPBACK_TEST /**< if defined, then this example will be a loopback test, which means that TX should be connected to RX to get data loopback. */
#define MAX_TEST_DATA_BYTES (15U) /**< max number of test bytes to be used for tx and rx. */
#define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */
void uart_error_handle(app_uart_evt_t * p_event)
{
if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_communication);
}
else if (p_event->evt_type == APP_UART_FIFO_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_code);
}
}
#define UART_HWFC APP_UART_FLOW_CONTROL_DISABLED
/**
* @brief Function for main application entry.
*/
int main(void)
{
uint32_t err_code;
uint32_t id1,id2;
id1=NRF_FICR->DEVICEID[0]; //读取id低31位
id2=NRF_FICR->DEVICEID[1];//读取id高31位
const app_uart_comm_params_t comm_params =
{
RX_PIN_NUMBER,
TX_PIN_NUMBER,
RTS_PIN_NUMBER,
CTS_PIN_NUMBER,
UART_HWFC,
false,
#if defined (UART_PRESENT)
NRF_UART_BAUDRATE_115200
#else
NRF_UARTE_BAUDRATE_115200
#endif
};
APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_error_handle,
APP_IRQ_PRIORITY_LOWEST,
err_code);
APP_ERROR_CHECK(err_code);
while (1)
{
printf("打印id:%lx%lx\r\n",id1,id2);
nrf_delay_ms(1000);
}
}
唯一 ID 用于加密
TEA 加密算法
唯一 ID 常用的场合就是加密,我们可以采用一个简单的加密算法对 ID 号进行加密。那么如果要正确运行程序就需要对唯一 ID 进行正确解密。
常用的代码加密方案一般有两种。
- 通过某种硬件手段防止单片机 FLASH 中的代码被读出,比如禁止读取、或者关闭下载接口;
- 就算代码能被读出来,把它烧到另一个芯片中,也无法正常运行(与特定芯片紧紧绑定)。
以目前的技术水平来说,不论如何禁止,似乎都有人可以把程序从芯片内部读取出来。那么就算被窃取者读取了程序的二进制文件,烧到另一个同型号的处理器芯片里,也必须无法运行。要实现这一目的,首先要有一个与单片机唯一绑定的东西,那么这就需要唯一 ID 号了,每一片芯片 ID 都不相同,并且全世界保存唯一。
研发者由唯一 ID 号通过加密算法计算得到检验码,然后向使用者下发。使用者可将此码通过专用编写的上位机把效验码烧写器写入到芯片的 EEPROM 中。 在代码中,可以在多个位置对 EEPROM 中的校验码进行比对,一致则正常运行,否则宕机。 比如在程序有最前面,一开始就进行鉴权,如果失败则向用户显示“无权限”等信息,停止程序运行;或是在程序中比较关键的条件分支中,这样如果程序被人破译,比如反汇编,通过修改一些条件判断,强行使其正常运行。因为程序中鉴权的地方越多,这势必让破解者费一些周折,但是也不能过多的地方出现检验码,避免被统计识别。 对于唯一 ID 的加密原理如下图
本节讲采用在安全学领域中常见的 TEA 加密算法进行加密和解密。所谓的 TEA(Tiny Encryption Algorithm)是一种分组加密算法,它的实现非常简单,通常只需要很精短的几行代码就可以实现,因此非常适合用单片机的加密中。
TEA 算法最初是由剑桥计算机实验室的 David Wheeler 和 Roger Needham 在 1994 年设计 的。TEA 算法使用 64 位的明文分组和 128 位的密钥,它使用 Feistel 分组加密框架,需要进行 64 轮 迭代。该算法使用了一个神秘常数δ作为倍数,它来源于黄金比率,以保证每一轮加密都不相同。但 δ的精确值似乎并不重要,这里 TEA 把它定义为 δ=「(√5 - 1)231」(也就是程序中的 0×9E3779B9)。
Tea 算法秘钥为 16 字节,每次分块处理的数据是 8 个字节,两个 32 位数据。加密过程中,加法运算和减法运算用作可逆的操作,算法轮流使用异或运算提供非线性特性,双移位操作使秘钥和数据的所有比特重复地混合,最多 16 轮循环就能使数据或密钥的单个比特的变化扩展到接近 32 比特. 因此 ,当循环轮数达到 16 轮以上时 ,该算法具有很强的抗差分攻击能力 ,128 比特密钥长度可以抗击穷举搜索攻击,该算法设计者推荐算法迭代次数为 32 轮。
唯一 ID 的加密和解密
下面简单的演示如何对唯一 ID 进行加密,搭建加密工程如下图
tea.c
代码如下:
#include "tea.h"
void encrypt (uint32_t* v, uint32_t* k)
{
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
void decrypt (uint32_t* v, uint32_t* k)
{
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}
主函数中,设置一组加密密码 key,本例简单的设置为 0x1234 作为密码。然后读取设备的唯一 ID 号,对唯一 ID 号进行 tea 加密。加密完成后的 ID 号可以作为程序加密的效验码。为了验证加密是否成功,再对加密后的唯一 ID 号进行解密。对比读取的唯一 ID 号和解密后的唯一 ID 号,如果两者相同,则证明加密算法正确。具体代码如下所示:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "app_uart.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "nrf.h"
#include "bsp.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif
#include "tea.h"
//#define ENABLE_LOOPBACK_TEST /**< if defined, then this example will be a loopback test, which means that TX should be connected to RX to get data loopback. */
#define MAX_TEST_DATA_BYTES (15U) /**< max number of test bytes to be used for tx and rx. */
#define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */
void uart_error_handle(app_uart_evt_t * p_event)
{
if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_communication);
}
else if (p_event->evt_type == APP_UART_FIFO_ERROR)
{
APP_ERROR_HANDLER(p_event->data.error_code);
}
}
#define UART_HWFC APP_UART_FLOW_CONTROL_DISABLED
/**
* @brief Function for main application entry.
*/
int main(void)
{
uint32_t err_code;
uint32_t id[2];
uint32_t key[]={0x1234,0x1234,0x1234,0x1234}; //密码
id[0]=NRF_FICR->DEVICEID[0]; //读取id低31位
id[1]=NRF_FICR->DEVICEID[1];//读取id高31位
const app_uart_comm_params_t comm_params =
{
RX_PIN_NUMBER,
TX_PIN_NUMBER,
RTS_PIN_NUMBER,
CTS_PIN_NUMBER,
UART_HWFC,
false,
#if defined (UART_PRESENT)
NRF_UART_BAUDRATE_115200
#else
NRF_UARTE_BAUDRATE_115200
#endif
};
APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_error_handle,
APP_IRQ_PRIORITY_LOWEST,
err_code);
APP_ERROR_CHECK(err_code);
while (1)
{
printf("打印id:%lx%lx\r\n",id[0],id[1]);
encrypt(id,key);//加密,工程的验证码
printf("加密id:%lx%lx\r\n",id[0],id[1]);
decrypt(id,key);//解密
printf("解密id:%lx%lx\r\n",id[0],id[1]);
printf("-------------------\r\n");
nrf_delay_ms(1000);
}
}