🐥

【ESP32】NVS暗号化キーを作成してNVSパーティションを暗号化

2024/06/05に公開

はじめに

方言を話す、おしゃべり猫型ロボット「ミーア」を開発中。

https://mia-cat.com/

前回、こちらの記事で、AWS IoT関連の設定ファイルをLittleFS領域からNVS領域に移動するのを実装した。

https://kazulog.fun/dev/esp32-platformio-config-to-nvs/

ただ、このままだとセキュリティ的に脆弱なので、今回はNVS暗号化の実装を試みる。

また、現状だとFlash EncryptionとSecure BootはArduino IDEでは提供されていないし、今後も提供する予定がないとのこと。

https://github.com/espressif/arduino-esp32/issues/9233

というわけで、前準備としてPlatformIOでArduinoだけではなくESP-IDFフレームワークも扱えるように下記実装を行った。

https://kazulog.fun/dev/esp32-platformio-arduino-espidf/

ようやく前準備ができたところで、NVS暗号化に取り掛かる。

NVS暗号化の必要性

NVS 暗号化を使用しない場合、フラッシュ チップに物理的にアクセスできる人なら誰でも、キーと値のペアを変更、消去、または追加することができてしまう。

NVS 暗号化を有効にすると、対応する NVS 暗号化キーを知らないと、キーと値のペアを変更または追加して有効なペアとして認識されることはない。

ESP32のNVS暗号化に関しては、EspressIf公式サイトの下記に詳細記載されている。

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_encryption.html

例はこちら

https://github.com/espressif/esp-idf/tree/c7bbfaee/examples/security/flash_encryption

ただ、今回はPlatformIOフレームワークでplatform.iniファイルでESP32の定義を行っているので、そちらに合わせる形で実装を進めていく。

NVSキーパーティションの作成

まず、ESP32のNVS暗号化機能を利用するために必要な暗号化キーを保存するための専用パーティションとして、新たにNVSキーパーティションを作成する。

そうすることで、nvs_flash_init APIは自動的に生成したNVS暗号化キーをNVSキーパーティションに保存するようになる。NVS暗号化が有効になっている場合、nvs_flash_init API関数は、最初のNVSキーパーティション、すなわちdataタイプとnvs_keysサブタイプのパーティションを見つける。その後、API関数は自動的にnvsキーをそのパーティションに生成して保存する。新しいキーは、該当するキーパーティションが空の場合にのみ生成および保存される。

なので、NVSキーパーティションとして、Typeはdata, Subtypeはnvs_keysを指定する必要がある。

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition

パーティションテーブル(partition.csv)

# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,      0x9000, 0x5000,
nvs_keys, data, nvs_keys, 0xe000, 0x2000, #nvsキーパーティションの生成
otadata,  data, ota,      0x10000, 0x2000,
app0,     app,  ota_0,    0x20000, 0x390000,
app1,     app,  ota_1,    0x410000, 0x390000,
spiffs,   data, spiffs,   0x800000, 0x800000

sdkconfigで、NVS暗号化を有効化する

PlatformIOではCONFIG_NVS_ENCRYPTIONビルドフラグを使用できない

https://esp32.com/viewtopic.php?t=38039

Arduino IDEでは、flash encryptionとsecure bootをサポートしていない

https://esp32.com/viewtopic.php?t=10029

上記が理由で、下記記事でframworkをespidfにも対応するようにした。

https://kazulog.fun/dev/esp32-platformio-arduino-espidf/

sdkconfigファイルに直接設定を追加する場合、以下の設定を確認または追加する

# Enable Flash encryption
CONFIG_SECURE_FLASH_ENC_ENABLED=y

# Flash encryption mode
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y  # 開発モードの場合
# CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y   # リリースモードの場合

# Enable NVS encryption
CONFIG_NVS_ENCRYPTION=y

NVS暗号化キー生成

暗号化NVSパーティションの作成

nvs_partition_gen.pyencryptコマンドを使用して、一度にNVS暗号化キーの生成と暗号化されたNVSパーティションの作成ができる。

下記のようなコマンドを実行する

python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 --keygen --keyfile sample_keys.bin

スクリーンショット 2024-05-27 13.21.31.png

これにより、指定したCSVファイルから暗号化されたNVSパーティション(sample_encr.bin)を生成し、同時に暗号化キー(sample_keys.bin)もkeys/ディレクトリ下に生成される。

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_partition_gen.html

# extra_script.py
import os
import subprocess
from SCons.Script import Import

Import("env")

# NSS暗号化キーの生成
def generate_nvs_key(source, target, env):
    print("Generating NVS encryption key...")
    key_file = "nvs_keys.bin"
    csv_file = "certificates/nvs.csv"
    bin_file = "certificates/encrypted_nvs_partition.bin"
    size = "0x5000"
    
    command = [
        "python",
        os.path.join(os.getenv('IDF_PATH'), 'components', 'nvs_flash', 'nvs_partition_generator', 'nvs_partition_gen.py'),
        "encrypt",
        csv_file,
        bin_file,
        size,
        "--keygen",
        "--keyfile", key_file
    ]
    result = subprocess.run(command, check=True)
    if result.returncode != 0:
        print("Error: NVS encryption key generation failed.")
    else:
        print("NVS encryption key generated successfully.")

# NVS暗号化キーの生成
env.AddPreAction("upload", generate_nvs_key)

このコマンドを実行して、keys/ディレクトリ下にnvs_keys.binが生成されたことを確認した。

NVSパーティションの生成と暗号化

次に、生成したNVS暗号化キーを用いて、NVSパーティションを暗号化する。encryptコマンドと—inputkeyコマンドを利用して、—inputkeyの引数として生成されたNVS暗号化キーを渡す。

# NVSパーティション生成と暗号化
def generate_encrypted_nvs_partition(source, target, env):
    print("Generating and encrypting NVS partition...")
    csv_file = "certificates/nvs.csv"
    bin_file = "certificates/encrypted_nvs_partition.bin"
    key_file = "keys/nvs_keys.bin"
    size = "0x5000"

    command = [
        "python",
        os.path.join(os.getenv('IDF_PATH'), 'components', 'nvs_flash', 'nvs_partition_generator', 'nvs_partition_gen.py'),
        "encrypt",
        csv_file,
        bin_file,
        size,
        "--inputkey", key_file
    ]
    result = subprocess.run(command, check=True)
    if result.returncode != 0:
        print("Error: NVS partition generation and encryption failed.")
    else:
        print("NVS partition generated and encrypted successfully.")

# NVS暗号化キーを使用して暗号化されたNVSパーティションを生成
env.AddPreAction("upload", generate_encrypted_nvs_partition)

cryptographyパッケージのインストール

ちなみに、nvs_partition_gen.py スクリプトは cryptography パッケージに依存しているのでインストールする(warningが出た場合は)。仮想環境下の場合は下記。

python -m venv venv
source venv/bin/activate
pip install cryptography

requirements.txtにcryptographyを追加しておく。

Detected overlap at address: 0x8000 for fileエラー対応

ビルドしたところ、下記エラーが出現

esptool write_flash: error: argument <address> <filename>: Detected overlap at address: 0x8000 for file: /Users/ky/dev/clocky/clocky_platformio/.pio/build/debug/partitions.bin

パーティションテーブルでoverlapは生じていないはずなのに、と思ってググったら、同じエラーに遭遇していた人いた。

https://community.platformio.org/t/esp-boot-partitions-overlap-error-help-with-compiler-config-options/12477

2つの解決策があるとのこと

続きは、こちらで記載しています。
https://kazulog.fun/dev/esp32-nvs-flash-encryption/

Discussion