单片机bootloader方案详解(AES、gzip、MD5)(一)
单片机bootloader方案(一)
方案简介
boot loader(引导加载程序)的主要目的是启动应用(APP)程序(或操作系统)。在单片机boot loader中,当单片机上电或复位时执行boot loader,然后它将在指定的FLASH区域中寻找APP程序,并且将控制权传递给APP程序(或操作系统)。
在计算机的boot程序中boot loader负责初始化计算机硬件,然后转去操作系统执行应用操作。而在单片机的资源有限不能在boot loader中将所有的硬件进行初始化(很多时候是需要低功耗处理的),所以单片机的boot loader主要的作用是更新固件和跳转执行固件。
在诸多的单片机boot loader中对固件安全和固件完整性检查的几乎没有,但这又是在软件安全中不可或缺的部分,所以这才有了本文对固件更新的boot loader中支持固件加密、压缩(固件非常大的情况下使用)、MD5校验等功能。
1.为什么选择压缩
在APP固件较大时应该使用gzip对固件进行压缩,其目的是为了加快固件在存储固件的设备与单片机间的传输,同时减少单片机flash中对更新固件(已压缩)的存储空间,降低成本。
2.为什么选择加密
在存储固件的设备与单片机间的传输过程中或压缩的固件被第三方非法获取后,第三方可以直接对硬件抄板并且直接下载非法获取的软件,产生盗版。应当对固件进行加密,防止第三方获取软件后直接使用,增加第三方解密成本(防盗版)。
3.为什么选择完整性校验
在固件传入单片机后可能在数据传输的过程中有各种干扰或者固件还没完全下载到单片机时,固件下载者强制取消下载,这将造成固件非完全版,如果解压缩和解密后直接运行将造成巨大的损失,可能会造成一定的安全隐患,对固件的完整性校验是必不可少的。
4.上位机参考
针对单片机的固件需要通过上位机对固件进行加密、压缩和文件校验,配套上位机可以参考:单片机固件MD5校验、AES加密、gzip压缩方案(QT)
5.单片机固件处理
在上位机中对固件处理的顺序如下:先使用glib对固件进行压缩;然后使用MD5对固件进行添加头部校验值;最后使用AES对固件进行加密。在单片机对固件的处理就应该是一个反向的操作流程,先对固件进行AES解密,然后使用gzip对固件进行解压缩,最后使用MD5对固件进行校验,校验无误则跳转运行。
以上的描述来看似乎将QT的固件处理内容移植到单片机中就好了,但是单片机的内存是非常有限的,所以在单片机中对固件的处理不是那么简单的,这一篇对这一算法有讲解,请往下看。
固件再整个处理过程中的数据形式(这只是最简单的形式,实际的数据形式会更加复杂):
一、单片机对固件解密
AES加密,全称Advanced Encryption Standard,是一种对称的分组加密技术。它使用128位分组加密数据,每次加密16个字节,提供比WEP/TKIPS的RC4算法更高的加密强度。
需要关注的点是按16字节加密,每次加密16字节,这表示解密时需要按16字节为单位对固件进行解密。
1.框图
AES解密是按照16字节为单位进行解密的,但是再这一步需要考虑到对压缩文件的处理,所以解密和解压是交替多次运行的,解密的开始是需要先解密出块数据信息(压缩文件的头部信息),块信息应该包含每一个块的大小(解压前大小,这将对解密时申请多少空间很重要),块的数量,以及在内存中每一个块的偏移地址(存储偏移地址是非常重要的,这是确保解压每一个块完整性的基础),自此gzip的头部信息获取完成,接下来是对数据进行AES和gzip的交替处理。
接着根据上面获取的gzip的头部信息,获得压缩后的每一个块数据的大小,根据块数据大小进行AES的16Byte字节对齐,依次对块数据进行解密和解压处理,将处理后的结果存入对于的按一定偏移的flash中,直至解密和解压完成整个文件。
2.算法(基于库)
/// 这里只贴出核心代码
/// 作为数据对齐的数据单元
struct
{
uint8_t block_flag; ///< 是否启用最后一个块的存储
uint8_t block_last_flag;///< flag 缓存
uint8_t block_pointer; ///< 下一个块的起始指针
uint8_t block_last_pointer; ///< 下下个块的起始指针缓存
uint8_t block_data[16]; ///< 最后一个块的解压数据留存,以备后续使用
}block_last; ///< 这个命名的block是只AES的16Byte为一个block
/**
* @brief 将AES加密数据进行16字节对齐
* @param size 数据总大小
* @param *pointer 上一个数据量的长度
* @return null
*
*/
uint32_t alignAESDataLen(uint32_t size, uint8_t *pointer)
{
BOOT_ASSER(!size);
BOOT_ASSER(!pointer);
uint8_t data;
*pointer = 0;
if(block_last.block_flag)
{
/// 减去上一个所占用的长度
*pointer = block_last.block_pointer;
if(size > AES_BLOCKLEN)
{
size = size + *pointer - AES_BLOCKLEN;
}
/// 获取上一次最后一个16Byte中包含当前的数据量
*pointer = AES_BLOCKLEN - *pointer;
}
data = size%AES_BLOCKLEN;
if(data != 0)
{
block_last.block_last_flag = 1;
block_last.block_last_pointer = data;
size = (AES_BLOCKLEN - (data)) + size;
}
else
{
block_last.block_last_flag = 0;
}
if(*pointer != 0)
{
size += *pointer;
}
return size;
}
/**
* @brief 对数据进行解密和数据缝合
* @param *buff 必须是一个大于等于Block块大小的block
* @param size 数据长度必须大于等于块大小(需要使用alignAESDataLen获取对齐之后的大小)
* @param pointer 上一个AES块中的包含当前gzip块的数量
* @return null
*
*/
void decryptAESBlockData(uint8_t *buff, uint32_t size, const uint8_t pointer)
{
BOOT_ASSER(!buff);
decryptAESData(&buff[pointer], size-pointer);
if(pointer != 0)
{
memcpy(buff,&block_last.block_data[AES_BLOCKLEN-pointer],pointer);
}
/// 保存块尾16Byte数据到block_last
saveBlockTailData(buff, size);
}
二、单片机对固件进行解压缩
zlib是一套通用的解压缩开源库,提供了内存(in-memory)压缩和解压函数,能检测解压出来的数据完整性,支持读写gzip(.gz)格式的文件。该库使用DEFLATE算法压缩数据部分,这是一种Huffman编码的加强。
需要特别注意的是zilb是对压缩的一整个块进行解压而不是将一个压缩块分成多个快进行解压,所以在上位机中就需要对一个固件进行按大小分块进行压缩。
还需要注意的是源固件按固定大小分块后经过压缩的各个块大小是不一样的,这种不一样是需要通过固件一起传输给单片机的,必须告诉单片机压缩后的数据大小是多少,解压时才能游刃有余,否则就是一眼黑抓瞎。
1.框图
2.算法(基于库)
三、单片机对固件进行完整性校验
MD5,全称Message Digest Algorithm 5,即信息摘要算法5,是一种被广泛使用的密码散列函数。它可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在RFC 1321标准中被加以规范。
需要特别注意的是MD5是需要对固件一整个进行计算不能分块计算MD5值进行合并,这是很好解决的,等待AES和gzip对固件处理后存储到flash另一个区域后,再使用MD5对一整个固件大小的flash进行取值计算即可。