概要
Espressifが提供しているesptoolを使えば、FLASHへのR/Wアクセスが簡単に行えます。
自身も、製造時・開発時におけるFLASHの書き込みはこのツールで実現していますが、以下のような懸念があるかと。
- フラッシュ内をそっくりそのまま読み出すことが可能(他者に不正にクローンを作られる)
- コードに含まれる秘匿な情報・ビジネスロジック等を読まれる
- 不正にFWを書き換えられ、IoTデバイスを踏み台にされる
などなど。。。
こういった懸念への解決策として、フラッシュ暗号化機能を提供してくれています。
これは、eFuseと呼ばれる、OTP(ワンタイムプログラム)メモリの一種を使用して実現しており、
フラッシュメモリ上の所定のパーティションの内容を AES-256 etc.. で暗号化するものです。
(ただし、適用すると書き込み周りが面倒くさくなるので、プロダクトとして完成段階での適用を想定しておくとよさそう)
今回はこの辺りについて色々実験してみた、と言う感じの雑多なメモです。
前提
ESP32-S3
の16MB Flashを仕様しています。
※eFuseの名称がESP32
と若干異なる箇所があります。
実験メモ
esp-idfの設定でフラッシュ暗号化周りの設定を適宜有効にする必要があります。
- 暗号化キーはESP32で自動生成させることも可能
- ホストマシンで事前に暗号化キーを生成する方法も可能(生成後、eFuseキーブロックに書き込む)
今回は事前に暗号化キーを生成する方法で。
espsecure.py generate_flash_encryption_key ./my_flash_encryption_key.bin
フラッシュ暗号化キーをeFuseに書き込む
--do-not-confirmでBURN署名はいらなくできるよ
espefuse.py --port /dev/tty.usbmodem11401 burn_key --show-sensitive-info BLOCK_KEY0 my_flash_encryption_key.bin XTS_AES_128_KEY
espefuse.py v4.6.2
Connecting...
Detecting chip type... ESP32-S3
=== Run "burn_key" command ===
Burn keys to blocks:
- BLOCK_KEY0 -> [ce 8e 91 38 35 bb 38 a1 49 7c 6f ef d8 64 e5 a0 57 f7 e6 f9 cd 42 c6 3d ab 60 58 c0 dd 50 39 8e]
Reversing byte order for AES-XTS hardware peripheral
'KEY_PURPOSE_0': 'USER' -> 'XTS_AES_128_KEY'.
Disabling write to 'KEY_PURPOSE_0'.
Disabling read to key block
Disabling write to key block
Check all blocks for burn...
idx, BLOCK_NAME, Conclusion
[00] BLOCK0 is not empty
(written ): 0x0000000000000100000000000000d1f50000000000000000
(to write): 0x000000000000000000000000040000000000000100800100
(coding scheme = NONE)
[04] BLOCK_KEY0 is empty, will burn the new value
.
This is an irreversible operation!
Type 'BURN' (all capitals) to continue.
BURN
BURN BLOCK4 - OK (write block == read block)
BURN BLOCK0 - OK (all write block bits are set)
Reading updated efuses...
Successful
esp-idfでフラッシュ暗号化の有効化に必要な設定
CONFIG_SECURE_FLASH_ENC_ENABLED
CONFIG_FLASH_ENCRYPTION_ENABLED=y
CONFIG_FLASH_ENCRYPTION_INSECURE=y
CONFIG_FLASH_ENCRYPTION_UART_BOOTLOADER_ALLOW_ENCRYPT=y
CONFIG_FLASH_ENCRYPTION_UART_BOOTLOADER_ALLOW_CACHE=y
有効化すると、ブートローダーのサイズが大きくなります。おそらく以下のようなエラー文で怒られるので、
適宜パーティションの調整を行ないましょう。
Error: Bootloader binary size 0x8920 bytes is too large for partition table offset 0x8000. Bootloader binary can be maximum 0x8000 (32768) bytes unless the partition table offset is increased in the Partition Table section of the project configuration menu.
フラッシュ暗号化を無効にしたいとき
以下のコマンドで一度有効化した設定を無効に出来ます。
espefuse.py --port /dev/tty.usbmodem113101 burn_efuse SPI_BOOT_CRYPT_CNT
eFuseの状態を見ると
SPI_BOOT_CRYPT_CNT (BLOCK0) Enables flash encryption when 1 or 3 bits are set = Disable R/W (0b011)
and disabled otherwise
偶数Bitが立っているので、暗号化を無効にできています。
(あと、1bitしかないので、もう一度有効にするともう解除できない。)
保存領域の暗号化
FLASH上で純粋なストレージ領域として使っているエリアはどうなるでしょうか。
以下のようなパーティションで運用していたとします。
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0xa000, 0x3000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
ota_0, app, ota_0, 0x10000, 4M,
ota_1, app, ota_1, 0x410000, 4M,
storage_0, data, spiffs, 0x810000, 0x7E9000,
storage_1, data, spiffs, 0xFF9000, 0x7000,
storage_0、storage_1のエリアについて、
稼働するファームウェアが必要とする何かしらのデータをフラッシュに保存している時、
プレーンなバイナリだとそのまま読み出されてしまう恐れがあります。
例えば、AWS等のクラウドと接続するための証明書のようなセキュアな情報が読み出されてしまうでしょう。
このような場合、eFuseに書き込んだ暗号化キーで、バイナリを暗号化させ、esptoolで書くことが可能です。
nvsの領域は暗号化することができないようです。
暗号化
espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0xff9000 --output endpoint-enc.bin endpoint.bin
espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0xffa000 --output thing_name-enc.bin thing_name.bin
espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0xffb000 --output hardware_ver-enc.bin hardware_ver.bin
espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0xffc000 --output root_ca_pem-enc.bin root_ca_pem.bin
espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0xffd000 --output device_cert-enc.bin device_cert.bin
espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0xffe000 --output private_key-enc.bin private_key.bin
espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0xfff000 --output ota_code_sign_cert-enc.bin ota_code_sign_cert.bin
書き込み
esptool.py --port /dev/cu.wchusbserial589B0379501 -b 460800 --before default_reset --after no_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0xff9000 endpoint-enc.bin
esptool.py --port /dev/cu.wchusbserial589B0379501 -b 460800 --before default_reset --after no_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0xffA000 thing_name-enc.bin
esptool.py --port /dev/cu.wchusbserial589B0379501 -b 460800 --before default_reset --after no_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0xffB000 hardware_ver-enc.bin
esptool.py --port /dev/cu.wchusbserial589B0379501 -b 460800 --before default_reset --after no_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0xffC000 root_ca_pem-enc.bin
esptool.py --port /dev/cu.wchusbserial589B0379501 -b 460800 --before default_reset --after no_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0xffD000 device_cert-enc.bin
esptool.py --port /dev/cu.wchusbserial589B0379501 -b 460800 --before default_reset --after no_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0xffE000 private_key-enc.bin
esptool.py --port /dev/cu.wchusbserial589B0379501 -b 460800 --before default_reset --after no_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0xffF000 ota_code_sign_cert-enc.bin
長さが足りず、怒られる場合があります。
適宜末尾にパディングをいれて調整してください。
A fatal error occurred: Input data length (15) must be a multiple of 16
```
実行するファームウェアからフラッシュ内へのアクセスについて
暗号化して書き込んだデータは、esp_flash_read_encrypted()
関数で復号化された状態で読み出し可能です。
参考文献