ESP32での暗号化・復号化処理(難読化)

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

概要

ESP32では、ハードウエアでの暗号化が可能です。内部のセキュアな場所に暗号化キーを保存する方式もありますが、今回は暗号化キーをプログラム内部に保存する、あまり安全でない簡易式(難読化)の方式での、AES CBC暗号化を検証しました。

ESP32での暗号化キーについて

ESP32は外部から読み出せない領域に暗号化キーを保存することが可能です。1度しか書き込めないOTP領域に書き込むことで、安全な暗号化を可能としています。

ただし、この扱いが非常に難しく、1度しか書き込めないので失敗すると、そのESP32では暗号化できなくなります。またArduino IDEではサポートしていないため、外部ツールを利用してプログラムを転送する必要があります。

今回は暗号化キーをプログラム内部に保存する方式での、暗号化と復号化を検証しました。

サンプルスケッチ

#include <hwcrypto/aes.h>

// AES CBCで暗号化
void encodeAes(const uint8_t key[32], uint8_t iv_base[16], char* plaintext, char* encrypted) {
  // IVは変更されるのでコピーを作成
  uint8_t iv[16];
  memcpy( iv, iv_base, sizeof( iv ) );

  // 暗号化
  esp_aes_context ctx;
  esp_aes_init( &ctx );
  esp_aes_setkey( &ctx, key, 256 );
  int len = ( ( strlen(plaintext) + 15 ) / 16 ) * 16;
  esp_aes_crypt_cbc( &ctx, ESP_AES_ENCRYPT, len, iv, (uint8_t*)plaintext, (uint8_t*)encrypted );
  esp_aes_free( &ctx );
}

// AES CBCで復号化
void decodeAes(const uint8_t key[32], uint8_t iv_base[16], char* plaintext, char* encrypted) {
  // IVは変更されるのでコピーを作成
  uint8_t iv[16];
  memcpy( iv, iv_base, sizeof( iv ) );

  // 復号化
  esp_aes_context ctx;
  esp_aes_init( &ctx );
  esp_aes_setkey( &ctx, key, 256 );
  int len = ( ( strlen(encrypted) + 15 ) / 16 ) * 16;
  esp_aes_crypt_cbc( &ctx, ESP_AES_DECRYPT, len, iv, (uint8_t*)encrypted, (uint8_t*)plaintext );
  esp_aes_free( &ctx );
}

// テキストダンプ
void dumpText(char *text ){
  // ダンプ
  for ( int i = 0; i < 512; i++ ) {
    if ( text[i] == 0 ) {
      break;
    }
    Serial.printf( "%02x[%c]%c", text[i], (text[i] > 31) ? text[i] : ' ', ((i & 0xf) != 0xf) ? ' ' : '\n' );
  }
  Serial.printf( "\n" );
}

void setup() {
  Serial.begin(115200);
  delay(1000);  // Serial初期化待ち

  // 暗号化キー
  uint8_t key[32];
  memset( key, 0, sizeof( key ) );

  // 暗号化ベクトル
  uint8_t iv[16];
  memset( iv, 0, sizeof( iv ) );

  // テキスト一時領域
  char plaintext[512];
  char encrypted[512];

  // 0クリア
  memset( plaintext, 0, sizeof( plaintext ) );
  memset( encrypted, 0, sizeof( encrypted ) );

  // 平文セット
  strcpy( plaintext, "Password Text String" );

  // 暗号化
  encodeAes(key, iv, plaintext, encrypted);
  Serial.println("暗号化");
  dumpText(encrypted);

  // 念の為0クリア
  memset( plaintext, 0, sizeof( plaintext ) );

  // 復号化
  decodeAes(key, iv, plaintext, encrypted);
  Serial.println("復号化");
  dumpText(plaintext);
}

void loop() {
}

出力結果

暗号化
e5[⸮] 80[⸮] ad[⸮] 45[E] 9c[⸮] ad[⸮] a0[⸮] 86[⸮] ce[⸮] 49[I] d3[⸮] e8[⸮] 8e[⸮] 54[T] 4f[O] 6c[l]
2e[.] 6e[n] df[⸮] 53[S] 3d[=] c9[⸮] fb[⸮] fb[⸮] 3b[;] f0[⸮] e0[⸮] b8[⸮] 7f[] b3[⸮] f6[⸮] 41[A]

復号化
50[P] 61[a] 73[s] 73[s] 77[w] 6f[o] 72[r] 64[d] 20[ ] 54[T] 65[e] 78[x] 74[t] 20[ ] 53[S] 74[t]
72[r] 69[i] 6e[n] 67[g]

解説

情報が少ないだけで、暗号化と復号化は非常にかんたんに利用することができます。注意しないといけない点は、16バイト単位で処理を行うことです。

16バイトの足りない場合には、なにかのデータをパティングして処理を行います。

  encodeAes(key, iv, "PASSWORD", encrypted);

上記のように渡すことができますが、この場合PASSWORD\0の後の10バイト目から16バイト目までのデータが不明ですので、暗号化文字列がメモリ状況により異なります。ただし、復号化はできるので、本来パティングは1からの連番など実装によって異なるようです。

0埋めをしておくと、暗号化検証サイトなどで答え合わせをすることができます。

上記サイトで文字列を「Password Text String」、キーを「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」、IVを「00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00」にした結果が以下の画像です。

一番下の暗号化文字列が、Arduinoの出力結果と同じことが確認できます。

注意点

今回キーとIVが両方固定値の0ですので、暗号化強度は非常に低いです。この2つの値を適切に与えることで、暗号強度を高めることができます。

内部の読み取りできない領域に保存しておけば安全なのですが、かんたんにはできないのでプログラム内部にキーを保存することになると思います。

static uint8_t key[32] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                           0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
                           0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
                           0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
                         };

ただしキーそのものをプログラムに埋め込むと、フラッシュを抜きだされた場合にかんたんに特定されてしまう可能性があるので、ESP.getEfuseMac()などの値を使って端末ごとに異なるキーを自動生成して使うなどの工夫が必要になると思います。

まとめ

暗号化自体はかんたんに利用できますが、キーをどう運用するのかはちょっと考える必要があります。実装例を載せてしまうと、それをそのまま使った暗号化テキストが、かんたんに復号化できてしまう可能性があるので、載せないこととします。

実際のユースケースとしてNVSに暗号化したパスワードなどを保存などが考えられ、NVSだと暗号化テキスト自体はかんたんに抜き出されることを想定してください。

キーとIVは、特定番号からの連番や、飛び番号にするだけでも、暗号化しないよりは強度が高まると思います。ただコードの中にキーがあるので、逆アセンブラしてコード自体を調べられるとかんたんではないですが、ハックすることが可能と思います。

コメント