Flash 加密
概述
使能 flash encryption 后,使用物理手段(如串口)从 SPI flash 中读取的数据都是经过加密的,大部分数据无法恢复出真实数据。
flash encryption 使用 256-bit AES key 加密 flash 数据,key 保存在芯片的 efuse 中,生成之后变成软件读写保护。
用户烧写 flash 时烧写的是数据明文,第一次 boot 时,软件 bootloader 会对 flash 中的数据在原处加密。
一般使用情况下一共有4次机会通过串口烧写 flash ,通过 OTA 更新 flash数据没有次数限制。开发阶段可以在 menuconfig中设置无烧写次数限制,但不要在产品中这么做。
使用步骤
make menuconfig 中选择 “Security features”->”Enable flash encryption on boot”
按通常操作编译出 bootloader, partition table 和 app image 并烧写到 flash 中
第一次 boot 时 flash 中被指定加密的数据被加密(大的 partition加密过程可能需要花费超过1分钟) ,之后就可以正常使用被加密的flash数据。
加密过程(第一次 boot 时进行)
bootloader 读取到 efuse 中的 FLASH_CRYPT_CNT 为0,于是利用硬件随机数生成器产生加密用的 key ,此 key 被保存在 efuse 中,对于软件是读写保护的。
bootloader 对所有需要被加密的 partition 在 flash 中原处加密
默认情况下 efuse 中的 DISABLE_DL_ENCRYPT, DISABLE_DL_DECRYPT 和 DISABLE_DL_CACHE 会被烧写为1,这样 UART bootloader 时就不能读取到解密后的 flash 数据
efuse 中的 FLASH_CRYPT_CONFIG 被烧写成 0xf,此标志用于决定加密 key 的多少位被用于计算每一个 flash 块(32字节)对应的秘钥,设置为 0xf 时使用所有256位
efuse 中的 FLASH_CRYPT_CNT 被烧写成 0x01,此标志用于 flash 烧写次数限制以及加密控制,详见“FLASH_CRYPT_CNT”一节
bootloader 将自己重启,从加密的 flash 执行软件 bootloader
串口重烧 flash (3次重烧机会)
串口重烧 flash 过程
make menuconfig 中选择 “Security features”->”Enable flash encryption on boot”
编译工程,将所有之前加密的 images (包括 bootloader)烧写到 flash 中。
在 esp-idf 的 components/esptool_py/esptool 路径下使用命令
espefuse.py burn\_efuse FLASH\_CRYPT\_CNT
烧写 efuse 中的 FLASH_CRYPT_CNT重启设备,bootloader 根据 FLASH_CRYPT_CNT 的值重新加密 flash 数据。
若用户确定不再需要通过串口重烧 flash,可以在 esp-idf 的
components/esptool\_py/esptool
路径下使用命令espefuse.py --port PORT write\_protect\_efuse FLASH\_CRYPT\_CNT 将 FLASH\_CRYPT\_CNT
设置为读写保护(注意此步骤必须在 bootloader 已经完成对 flash 加密后进行)
FLASH_CRYPT_CNT
FLASH_CRYPT_CNT 是 flash 加密方案中非常重要的控制标志,它是 8-bit 的值,它的值一方面决定 flash 中的值是否马上需要加密,另一方面控制 flash 烧写次数限制。
当 FLASH_CRYPT_CNT 有(0,2,4,6,8)位被烧写为1时,bootloader 会对 flash 中的内容进行加密。
当 FLASH_CRYPT_CNT 有(1,3,5,7)位被烧写为1时,bootloader 知道 flash 的内容已经过加密,直接读取 flash 中的数据解密后使用。
FLASH_CRYPT_CNT 的变化过程:
没有使能 flash 加密时,永远是0
使能了 flash 加密,在第一次 boot 时 bootloader 发现它的值是 0x00,于是知道 flash 中的数据还未加密,利用硬件随机数生成器产生 key,然后加密 flash,最后将它的最低位置1(取值为0x01)
后续 boot 时,bootloader 发现它的值是 0x01,知道 flash 中的数据已加密,可以解密后直接使用
用户需要串口重烧 flash ,于是使用命令行手动烧写 FLASH_CRYPT_CNT,此时2个 bit 被置为 1(取值为0x03)
重启设备,bootloader 发现 FLASH_CRYPT_CNT 的值是 0x03(2 bit 1),于是重新加密 flash 数据,加密完成后 bootloader 将 FLASH_CRYPT_CNT 烧写为0x07(3 bit 1),flash 加密正常使用
用户需要串口重烧 flash ,于是使用命令行手动烧写 FLASH_CRYPT_CNT,此时4个 bit 被置为 1(取值为0x0f)
重启设备,bootloader 发现 FLASH_CRYPT_CNT 的值是 0x0f(4 bit 1),于是重新加密 flash 数据,加密完成后 bootloader 将 FLASH_CRYPT_CNT 烧写为0x1f(5 bit 1),flash 加密正常使用
用户需要串口重烧 flash ,于是使用命令行手动烧写 FLASH_CRYPT_CNT,此时6个 bit 被置为 1(取值为0x3f)
重启设备,bootloader 发现 FLASH_CRYPT_CNT 的值是 0x4f(6 bit 1),于是重新加密 flash 数据,加密完成后 bootloader 将 FLASH_CRYPT_CNT 烧写为0x7f(7 bit 1),flash 加密正常使用
注意!此时不能再使用命令行烧写 FLASH_CRYPT_CNT,bootloader 读到 FLASH_CRYPT_CNT 为 0xff(8 bit 1)时,会停止后续的 boot。
被加密的数据
Bootloader
Secure boot bootloader digest(若 Secure Boot 被使能, flash 中会多出这一项, 具体查看“Secure Boot”中“执行过程”的步骤3)
Partition table
Partition table 中指向的所有 Type 域标记为“app”的部分
Partition table 中指向的所有 Flags 域标记为“encrypted”的部分(用于非易失性存储(NVS)部分的 flash 在任何情况下都不会被加密)
哪些方式读到解密后的数据(真实数据)
通过内存管理单元的 flash 缓存读取的 flash 数据都是经过解密后的数据,包括:
flash 中的可执行应用程序代码
存储在 flash 中的只读数据
任何通过
API esp\_spi\_flash\_mmap()
读取的数据由 ROM bootloader 读取的软件 bootloader image 数据
如果调用 API
esp\_partition\_read()
读取被加密区域的数据,则读取的 flash 数据是经过解密后的数据
哪些方式读到不解密的数据(无法使用的脏数据)
通过 API esp_spi_flash_read() 读取的数据
ROM 中的函数 SPIRead() 读取的数据
软件写入加密数据
调用 API
esp\_partition\_write()
时,只有写到被加密的 partition 的数据才会被加密函数
esp\_spi\_flash\_write()
根据参数write\_encrypted
是否被设为 true 决定是否对数据加密ROM 函数
esp\_rom\_spiflash\_write\_encrypted()
将加密后的数据写入 flash 中,而 SPIWrite() 将不加密的数据写入到 flash 中