Arduino core for the ESP32の暗号化検証

現時点の情報です。最新情報はM5StickC非公式日本語リファレンスを確認してみてください。

概要

ESP32の暗号化機能を検証しました。

ESP-IDFですと、正式サポートされていますが、Arduino coreでは暗号化処理や転送をコマンドラインで行う必要がありました。

暗号化とは

ESP32は内部に暗号化キーを保存することができ、その暗号化キーを利用してフラッシュメモリの暗号化が可能です。

暗号化キーは外部からアクセスできない領域に保存されており、一度だけしか書き込むことができません。

暗号化キー作成

C:\Temp>espsecure.py generate_flash_encryption_key ./key.bin
espsecure.py v2.7
Writing 256 random bits to key file ./key.bin

上記コマンドで暗号化キーを作成します。内容は32バイトのランダムな数値のバイナリファイルです。この暗号化キーは端末ごとに変更するのが推奨で、MACアドレスなどでファイル名をつけて保存しておいたほうが安全です。

同じキーを使い回すことも可能ですが、そのキーですべて暗号化できてしまうので、キーの管理には気をつけてください。

プログラム作成と転送

普通にプログラムを作成します。環境設定の「より詳細な情報を表示する」の「書き込み」をチェックしてファイル転送をしてください。

最大1310720バイトのフラッシュメモリのうち、スケッチが734366バイト(56%)を使っています。
最大327680バイトのRAMのうち、グローバル変数が43216バイト(13%)を使っていて、ローカル変数で284464バイト使うことができます。
C:\Users\%USERNAME%\AppData\Local\Arduino15\packages\esp32\tools\esptool_py\2.6.1/esptool.exe --chip esp32 --port COM4 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xe000 C:\Users\%USERNAME%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4/tools/partitions/boot_app0.bin 0x1000 C:\Users\%USERNAME%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4/tools/sdk/bin/bootloader_dio_80m.bin 0x10000 C:\Users\%USERNAME%\AppData\Local\Temp\arduino_build_282952/BasicOTA_WL.ino.bin 0x8000 C:\Users\%USERNAME%\AppData\Local\Temp\arduino_build_282952/BasicOTA_WL.ino.partitions.bin 
esptool.py v2.6
Serial port COM4
Connecting........__
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: ??:??:??:??:??:??
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 8192 bytes to 47...
Writing at 0x0000e000... (100 %)
Wrote 8192 bytes (47 compressed) at 0x0000e000 in 0.0 seconds (effective 5957.7 kbit/s)...
Hash of data verified.
Compressed 15856 bytes to 10276...
Writing at 0x00001000... (100 %)
Wrote 15856 bytes (10276 compressed) at 0x00001000 in 0.1 seconds (effective 961.0 kbit/s)...
Hash of data verified.
Compressed 734480 bytes to 412789...
Writing at 0x00010000... (3 %)
Writing at 0x00014000... (7 %)
Writing at 0x00018000... (11 %)
Writing at 0x0001c000... (15 %)
Writing at 0x00020000... (19 %)
Writing at 0x00024000... (23 %)
Writing at 0x00028000... (26 %)
Writing at 0x0002c000... (30 %)
Writing at 0x00030000... (34 %)
Writing at 0x00034000... (38 %)
Writing at 0x00038000... (42 %)
Writing at 0x0003c000... (46 %)
Writing at 0x00040000... (50 %)
Writing at 0x00044000... (53 %)
Writing at 0x00048000... (57 %)
Writing at 0x0004c000... (61 %)
Writing at 0x00050000... (65 %)
Writing at 0x00054000... (69 %)
Writing at 0x00058000... (73 %)
Writing at 0x0005c000... (76 %)
Writing at 0x00060000... (80 %)
Writing at 0x00064000... (84 %)
Writing at 0x00068000... (88 %)
Writing at 0x0006c000... (92 %)
Writing at 0x00070000... (96 %)
Writing at 0x00074000... (100 %)
Wrote 734480 bytes (412789 compressed) at 0x00010000 in 7.0 seconds (effective 838.9 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 128...
Writing at 0x00008000... (100 %)
Wrote 3072 bytes (128 compressed) at 0x00008000 in 0.0 seconds (effective 2457.6 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...

上記が転送ログです、上の方にある転送している行だけ抜き出します。

C:\Users\%USERNAME%\AppData\Local\Arduino15\packages\esp32\tools\esptool_py\2.6.1/esptool.exe
--chip
esp32
--port
COM4
--baud
921600
--before
default_reset
--after
hard_reset
write_flash
-z
--flash_mode
dio
--flash_freq
80m
--flash_size
detect
0xe000
C:\Users\%USERNAME%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4/tools/partitions/boot_app0.bin
0x1000
C:\Users\%USERNAME%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4/tools/sdk/bin/bootloader_dio_80m.bin
0x10000
C:\Users\%USERNAME%\AppData\Local\Temp\arduino_build_282952/BasicOTA_WL.ino.bin
0x8000
C:\Users\%USERNAME%\AppData\Local\Temp\arduino_build_282952/BasicOTA_WL.ino.partitions.bin

上記がスペースを改行に置き換えたものです。4つのファイルを転送しているのがわかります。

Arduinoはビルドした際にディレクトリが毎回変わりますので、ビルドログや転送ログを確認してどのディレクトリにファイルが作成されているかを確認します。

  • (0xe000)boot_app0.bin
  • (0x1000)bootloader_dio_80m.bin
  • (0x10000)BasicOTA_WL.ino.bin
  • (0x8000)BasicOTA_WL.ino.partitions.bin

上記の4つで、パーティション情報はデフォルトですので下の情報になります。

# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xe000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x140000,
app1,     app,  ota_1,   0x150000,0x140000,
spiffs,   data, spiffs,  0x290000,0x170000,

nvs(0x9000)

nvsは内部データ保存領域ですが、暗号化対象外です。ファイル転送をせずにプログラムから初期化して利用します。

otadata(0xe000)

OTAで起動するのがapp0かapp1かを保存する領域です。暗号化対象外です。

app0(0x10000)

実際のプログラム領域です。暗号化してから転送する必要があります。

app1(0x150000)

OTAで保存するプログラム領域です。最初はapp0を利用するので、app1は利用しません。

spiffs(0x290000)

SPIフラッシュを利用したファイル領域です。暗号化対象外です。利用する場合には通常と同じようにファイルを転送してください。

bootloader(0x1000)

標準領域なのでパーティションには情報がありませんが、2ndブートローダーの領域です。こちらも暗号化して転送する必要があります。

ESP32の状態取得

C:\Temp>espefuse.py -p COM4 summary
espefuse.py v2.7
Connecting....
EFUSE_NAME             Description = [Meaningful Value] [Readable/Writeable] (Hex Value)
----------------------------------------------------------------------------------------
Security fuses:
FLASH_CRYPT_CNT        Flash encryption mode counter                     = 0 R/W (0x0)
FLASH_CRYPT_CONFIG     Flash encryption config (key tweak bits)          = 0 R/W (0x0)
CONSOLE_DEBUG_DISABLE  Disable ROM BASIC interpreter fallback            = 1 R/W (0x1)
ABS_DONE_0             secure boot enabled for bootloader                = 0 R/W (0x0)
ABS_DONE_1             secure boot abstract 1 locked                     = 0 R/W (0x0)
JTAG_DISABLE           Disable JTAG                                      = 0 R/W (0x0)
DISABLE_DL_ENCRYPT     Disable flash encryption in UART bootloader       = 0 R/W (0x0)
DISABLE_DL_DECRYPT     Disable flash decryption in UART bootloader       = 0 R/W (0x0)
DISABLE_DL_CACHE       Disable flash cache in UART bootloader            = 0 R/W (0x0)
BLK1                   Flash encryption key
  = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLK2                   Secure boot key
  = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLK3                   Variable Block 3
  = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
Config fuses:
XPD_SDIO_FORCE         Ignore MTDI pin (GPIO12) for VDD_SDIO on reset    = 0 R/W (0x0)
XPD_SDIO_REG           If XPD_SDIO_FORCE, enable VDD_SDIO reg on reset   = 0 R/W (0x0)
XPD_SDIO_TIEH          If XPD_SDIO_FORCE & XPD_SDIO_REG, 1=3.3V 0=1.8V   = 0 R/W (0x0)
CLK8M_FREQ             8MHz clock freq override                          = 54 R/W (0x36)
SPI_PAD_CONFIG_CLK     Override SD_CLK pad (GPIO6/SPICLK)                = 0 R/W (0x0)
SPI_PAD_CONFIG_Q       Override SD_DATA_0 pad (GPIO7/SPIQ)               = 0 R/W (0x0)
SPI_PAD_CONFIG_D       Override SD_DATA_1 pad (GPIO8/SPID)               = 0 R/W (0x0)
SPI_PAD_CONFIG_HD      Override SD_DATA_2 pad (GPIO9/SPIHD)              = 0 R/W (0x0)
SPI_PAD_CONFIG_CS0     Override SD_CMD pad (GPIO11/SPICS0)               = 0 R/W (0x0)
DISABLE_SDIO_HOST      Disable SDIO host                                 = 0 R/W (0x0)
Identity fuses:
MAC                    Factory MAC Address
  = ??:??:??:??:??:?? (CRC 66 OK) R/W
CHIP_VER_REV1          Silicon Revision 1                                = 1 R/W (0x1)
CHIP_VERSION           Reserved for future chip versions                 = 2 R/W (0x2)
CHIP_PACKAGE           Chip package identifier                           = 0 R/W (0x0)
Calibration fuses:
BLK3_PART_RESERVE      BLOCK3 partially served for ADC calibration data  = 0 R/W (0x0)
ADC_VREF               Voltage reference calibration                     = 1107 R/W (0x1)
Efuse fuses:
WR_DIS                 Efuse write disable mask                          = 0 R/W (0x0)
RD_DIS                 Efuse read disablemask                            = 0 R/W (0x0)
CODING_SCHEME          Efuse variable block length scheme                = 0 R/W (0x0)
KEY_STATUS             Usage of efuse block 3 (reserved)                 = 0 R/W (0x0)
Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V).

必須ではありませんが、作業前にボードの状態を表示して、どこが変わったのかを確認します。

暗号化キーの書き込み

C:\Temp>espefuse.py -p COM4 burn_key flash_encryption ./key.bin
espefuse.py v2.7
Connecting....
Write key in efuse block 1. The key block will be read and write protected (no further changes or readback). This is an irreversible operation.
Type 'BURN' (all capitals) to continue.
BURN
Burned key data. New value: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Disabling read/write to key efuse block...

暗号化キーを書き込みます。一度書き込んだら変更ができません。失敗すると使うことができなくなるので注意してください。

上記は検証用に全部00で埋めた暗号化キーを書き込んでいます。

暗号化を有効にする

C:\Temp>espefuse.py -p COM4 burn_efuse FLASH_CRYPT_CNT
espefuse.py v2.7
Connecting......
Burning efuse FLASH_CRYPT_CNT (Flash encryption mode counter) 0x0 -> 0x1. This is an irreversible operation.
Type 'BURN' (all capitals) to continue.
BURN

暗号化を有効にします。立っているビット数が奇数の場合には暗号化が有効になります。再度実行すると0x03になって無効になります。最高4回暗号化の有効化と無効化ができますが、4回目に無効にしたところで、それ以上変更できなくなります。

基本的には1度有効化したものは無効化しない運用がおすすめです。

暗号化モード設定

C:\Temp>espefuse.py -p COM4 burn_efuse FLASH_CRYPT_CONFIG 15
espefuse.py v2.7
Connecting.....
Burning efuse FLASH_CRYPT_CONFIG (Flash encryption config (key tweak bits)) 0x0 -> 0xf. This is an irreversible operation.
Type 'BURN' (all capitals) to continue.
BURN

暗号化の強度を設定します。基本的には15で利用してください。

状態確認

C:\Temp>espefuse.py -p COM4 summary
espefuse.py v2.7
Connecting......
EFUSE_NAME             Description = [Meaningful Value] [Readable/Writeable] (Hex Value)
----------------------------------------------------------------------------------------
Security fuses:
FLASH_CRYPT_CNT        Flash encryption mode counter                     = 1 R/W (0x1)
FLASH_CRYPT_CONFIG     Flash encryption config (key tweak bits)          = 15 R/W (0xf)
CONSOLE_DEBUG_DISABLE  Disable ROM BASIC interpreter fallback            = 1 R/W (0x1)
ABS_DONE_0             secure boot enabled for bootloader                = 0 R/W (0x0)
ABS_DONE_1             secure boot abstract 1 locked                     = 0 R/W (0x0)
JTAG_DISABLE           Disable JTAG                                      = 0 R/W (0x0)
DISABLE_DL_ENCRYPT     Disable flash encryption in UART bootloader       = 0 R/W (0x0)
DISABLE_DL_DECRYPT     Disable flash decryption in UART bootloader       = 0 R/W (0x0)
DISABLE_DL_CACHE       Disable flash cache in UART bootloader            = 0 R/W (0x0)
BLK1                   Flash encryption key
  = ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? -/-
BLK2                   Secure boot key
  = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLK3                   Variable Block 3
  = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
Efuse fuses:
WR_DIS                 Efuse write disable mask                          = 128 R/W (0x80)
RD_DIS                 Efuse read disablemask                            = 1 R/W (0x1)
CODING_SCHEME          Efuse variable block length scheme                = 0 R/W (0x0)
KEY_STATUS             Usage of efuse block 3 (reserved)                 = 0 R/W (0x0)
Calibration fuses:
BLK3_PART_RESERVE      BLOCK3 partially served for ADC calibration data  = 0 R/W (0x0)
ADC_VREF               Voltage reference calibration                     = 1107 R/W (0x1)
Config fuses:
XPD_SDIO_FORCE         Ignore MTDI pin (GPIO12) for VDD_SDIO on reset    = 0 R/W (0x0)
XPD_SDIO_REG           If XPD_SDIO_FORCE, enable VDD_SDIO reg on reset   = 0 R/W (0x0)
XPD_SDIO_TIEH          If XPD_SDIO_FORCE & XPD_SDIO_REG, 1=3.3V 0=1.8V   = 0 R/W (0x0)
CLK8M_FREQ             8MHz clock freq override                          = 54 R/W (0x36)
SPI_PAD_CONFIG_CLK     Override SD_CLK pad (GPIO6/SPICLK)                = 0 R/W (0x0)
SPI_PAD_CONFIG_Q       Override SD_DATA_0 pad (GPIO7/SPIQ)               = 0 R/W (0x0)
SPI_PAD_CONFIG_D       Override SD_DATA_1 pad (GPIO8/SPID)               = 0 R/W (0x0)
SPI_PAD_CONFIG_HD      Override SD_DATA_2 pad (GPIO9/SPIHD)              = 0 R/W (0x0)
SPI_PAD_CONFIG_CS0     Override SD_CMD pad (GPIO11/SPICS0)               = 0 R/W (0x0)
DISABLE_SDIO_HOST      Disable SDIO host                                 = 0 R/W (0x0)
Identity fuses:
MAC                    Factory MAC Address
  = ??:??:??:??:??:?? (CRC 66 OK) R/W
CHIP_VER_REV1          Silicon Revision 1                                = 1 R/W (0x1)
CHIP_VERSION           Reserved for future chip versions                 = 2 R/W (0x2)
CHIP_PACKAGE           Chip package identifier                           = 0 R/W (0x0)
Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V).

暗号化キーが設定されており、読み出せないので??で表示されています。FLASH_CRYPT_CNTとFLASH_CRYPT_CONFIGも変更されていることを確認してください。

暗号化を有効化してあるので、この状態でESP32を起動しても、エラーがでて起動しません。

ファイル暗号化

C:\Temp>espsecure.py encrypt_flash_data --keyfile ./key.bin --address 0x1000 -o bootloader.bin bootloader_dio_80m.bin
espsecure.py v2.7
Using 256-bit key
C:\Temp>espsecure.py encrypt_flash_data --keyfile ./key.bin --address 0x10000 -o app.bin BasicOTA.ino.bin
espsecure.py v2.7
Using 256-bit key
C:\Temp>espsecure.py encrypt_flash_data --keyfile ./key.bin --address 0x8000 -o partitions.bin BasicOTA.ino.partitions.bin
espsecure.py v2.7
Using 256-bit key

各種必要なファイルをコピーしてきて、暗号化処理を行います。アドレスの引数はパーティションの開始アドレスに合わせる必要があります。

暗号化する必要があるのはotadataを除く3つのファイルです。

ファイル転送

C:\Temp>esptool.py --chip esp32 --port COM4 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xe000 boot_app0.bin 0x1000 bootloader.bin 0x10000 app.bin 0x8000 partitions.bin
esptool.py v2.7
Serial port COM4
Connecting....
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
WARNING: Detected crystal freq 41.01MHz is quite different to normalized freq 40MHz. Unsupported crystal in use?
Crystal is 40MHz
MAC: ??:??:??:??:??:??
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 8192 bytes to 47...
Wrote 8192 bytes (47 compressed) at 0x0000e000 in 0.0 seconds (effective 6571.6 kbit/s)...
Hash of data verified.
Warning: Image file at 0x1000 doesn't look like an image file, so not changing any flash settings.
Compressed 15856 bytes to 15867...
Wrote 15856 bytes (15867 compressed) at 0x00001000 in 0.2 seconds (effective 631.5 kbit/s)...
Hash of data verified.
Compressed 734480 bytes to 732412...
Wrote 734480 bytes (732412 compressed) at 0x00010000 in 9.7 seconds (effective 604.7 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 1801...
Wrote 3072 bytes (1801 compressed) at 0x00008000 in 0.0 seconds (effective 849.7 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...

ファイルを転送します。Arduinoと同じ転送オプションで4つのファイルを転送します。一度転送すればapp0以外は変更されませんので、個別に転送しても大丈夫です。

動作確認

この状態で再起動して、動いていれば成功です!

失敗した場合には再度やり直すか、暗号化を諦めてFLASH_CRYPT_CNTをBURNすれば普通の状態に戻ります。

OTA

OTAは試してみましたが、Arduino core for the ESP32ではサポートされていない気がします。転送後のファイル内容確認の際に、暗号化されていない前提でチェックをしており転送エラーとなります。

暗号化したファイルをOTAで転送すると、転送中の序盤に転送エラーになりました。細かい動きを追っていませんが、ソース非公開の場所でのエラーですのでちょっと対応が難しい気がします。

少し検証したところ、ESP32のアプリは先頭が0xE9で始まっている必要があり、OTAの際にチェックしています。転送に成功しても暗号化されている状態なので、OTA初回起動時にチェックが失敗してOTAロールバックが実行されていました。

Arduinoのブートローダーは暗号化後のOTAには対応していないようでした。

参考サイト

この記事は上記サイトに書かれている内容を、Arduino core for the ESP32に特化して整理しなおしています。

まとめ

Arduino coreでのフラッシュ暗号化はできました。しかしながらArduino IDEからかんたんに転送できないので、非常に開発が面倒です。

せめてOTAが使えたら便利なのですが、普通に転送したところNGでした。ESP-IDFだとOTAが使えるので、なにか方法があるとは思いますが、現状は使えませんでした。

現状は開発は普通に行って、製品化などで暗号化したい場合の出荷時に暗号化を行うのが好ましいと思います。ただNVS領域の暗号化は、暗号化対応のNVS関連関数を呼び出す必要がありますので、そこはコマンドラインで転送しながら検証する必要があると思います。

これ以外にセキュアブートという機能もあるので、もう少し検証を続けたいと思います。

コメント