ESP8266 で AES アルゴリズム・CBC モード・PKCS7 パディング による暗号化・復号をする方法を述べます。
目次
AES アルゴリズム・CBC モード とは?
AES は Advanced Encryption Standard の略で、DES に代わる共通鍵暗号として開発されたようです。共通鍵暗号なので、暗号化の際と復号の際は同じ鍵を使います。鍵の長さは 16 Byte・24 Byte・32 Byte の中から選べます。AES はブロック暗号であり、ブロック長は 16 Byte です。
一方、CBC は Cipher Block Chaining Mode の略で、暗号ブロック連鎖モードらしいです。AES はブロック暗号であるため、必ず 16 Byte ごとに暗号化をおこないます。CBC では、まず、平文の最初の 16 Byte と IV(Initialization Vector)と呼ばれる 16 Byte の X-OR をとり、AES アルゴリズムで暗号化します。そして、平文の次の 16 Byte とさっきの 16 Byte の暗号文の X-OR をとり、AES アルゴリズムで暗号化して……を繰り返します。
PKCS7 パディングとは?
AES は 16 Byte ブロック暗号なので、平文のサイズは 16 Byte の倍数である必要があります。なので、平文の長さを無理やり 16 の倍数にするために、PKCS#7 パディングというのを施します。
例えば、Gumi-note PKCS7 Example.
という 24 Byte の文字列があったとします。これを16進数の文字コードで表すと、次のようになります。
c++💩// "Gumi-note PKCS7 Example."
47 75 6D 69 2D 6E 6F 74 65 20 50 4B 43 53 37 20
45 78 61 6D 70 6C 65 2E
では、このデータを 16 の倍数 Byte にしたいと思います。元データの長さを $len$ とすると、PKCS#7 パディング後のブロック数は $len\div 16 + 1$、すなわち、$(len\div 16 + 1)\times 16$ Byte となります。つまり、上の 24 Byte のデータは、32 Byte に拡張されます。そして、$32-24=8$ Byte は、08
で埋められます。PKCS#7 パディング後のデータは次のようになるでしょう。
// "Gumi-note PKCS7 Example." After PKCS7 Padding.
47 75 6D 69 2D 6E 6F 74 65 20 50 4B 43 53 37 20
45 78 61 6D 70 6C 65 2E 08 08 08 08 08 08 08 08
では、次に示す32 Byte のデータはどうでしょうか?
// "Gumi-note PKCS7 Padding Example."
47 75 6D 69 2D 6E 6F 74 65 20 50 4B 43 53 37 20
50 61 64 64 69 6E 67 20 45 78 61 6D 70 6C 65 2E
このデータは、3 ブロック、すなわち 48 Byte に拡張され、次のようになります。10
は $48-32=16$ を 16進数表記したものです。
// "Gumi-note PKCS7 Padding Example." After PKCS7 Padding.
47 75 6D 69 2D 6E 6F 74 65 20 50 4B 43 53 37 20
50 61 64 64 69 6E 67 20 45 78 61 6D 70 6C 65 2E
10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
ESP8266 の暗号化ライブラリ
現在の ESP8266 Arduino ライブラリは、セキュアな SSL/TLS 通信をすることができるようになっています。SSL/TLS 通信をするために、基本的な暗号化アルゴリズムの実装が必要ですが、最近の ESP8266 Arduino ライブラリには BearSSL という C言語で書かれたライブラリを移植したものが含まれており、この素晴らしいライブラリのおかげで ESP8266 で SSL/TLS 通信が可能になっています。
BearSSL は、今回用いる AES-CBC 暗号化アルゴリズムや、RSA 暗号などを扱うことができるすぐれものです。一方、ドキュメントが少なく、その使い方は分かりづらい部分もあり、ソースコードを見る必要があるかもしれません。また、PKCS7 Padding などは実装されていない or 見つけられなかったので、自分で実装する必要があります。
AES-CBC PKCS#7 Padding 暗号化
次に示すプログラムで PKCS#7 パディングと AES-CBC 方式の暗号化ができます。なお、このプログラムの実行には #include <ESP8266WiFi.h>
が必要です。ここでは共通鍵の長さは 16 Byte です。
// Input : plain_data & key & iv
String plain_data = "Gumi-note AES-CCBC PKCS7 Padding Example.";
int len = plain_data.length();
uint8_t key[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // 16 Byte Key now.
uint8_t iv[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int i;
// PKCS#7 Padding (Encryption), Block Size : 16
int n_blocks = len / 16 + 1;
uint8_t n_padding = n_blocks * 16 - len;
uint8_t data[n_blocks*16];
memcpy(data, plain_data.c_str(), len);
for(i = len; i < n_blocks * 16; i++){
data[i] = n_padding;
}
// AES CBC Encryption
// encryption context
br_aes_big_cbcenc_keys encCtx;
// reset the encryption context and encrypt the data
br_aes_big_cbcenc_init(&encCtx, key, 16); // 16 Byte Key now.
br_aes_big_cbcenc_run( &encCtx, iv, data, n_blocks*16 );
// ! Caution !
// In br_aes_big_cbcenc_run function, iv & data were overwritten.
// Output : Encrypted data
// return data;
// "B404EBE0BAFD429F3FF0A5CCE8638EFC663343BBBE5BBA26A1AE37F23807D5E3E16ABDF57B59A0B84A6A09252DA1FBC2"
AES-CBC PKCS#7 Padding 復号
次に示すプログラムで AES-CBC 方式の復号と PKCS#7 パディングの復元ができます。ここでは共通鍵の長さは 16 Byte です。
// Input : Encrypted data & key & iv
uint8_t data[] = {0xB4, 0x04, 0xEB, 0xE0, 0xBA, 0xFD, 0x42, 0x9F, 0x3F, 0xF0, 0xA5, 0xCC, 0xE8, 0x63, 0x8E, 0xFC, 0x66, 0x33, 0x43, 0xBB, 0xBE, 0x5B, 0xBA, 0x26, 0xA1, 0xAE, 0x37, 0xF2, 0x38, 0x07, 0xD5, 0xE3, 0xE1, 0x6A, 0xBD, 0xF5, 0x7B, 0x59, 0xA0, 0xB8, 0x4A, 0x6A, 0x09, 0x25, 0x2D, 0xA1, 0xFB, 0xC2};
int len = sizeof(data);
uint8_t key[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // 16 Byte Key now.
uint8_t iv[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
// AES CBC Decryption
br_aes_big_cbcdec_keys decCtx;
br_aes_big_cbcdec_init(&decCtx, key, 16);
br_aes_big_cbcdec_run( &decCtx, iv, data, len );
// ! Caution !
// In br_aes_big_cbcdec_run function, iv & data were overwritten.
// PKCS#7 Padding (Decryption)
int n_blocks = len / 16;
uint8_t n_padding = data[n_blocks*16-1];
len = n_blocks*16 - n_padding;
char plain_data[len + 1];
memcpy(plain_data, data, len);
plain_data[len] = '\0'; // Lang C string must end with '\0'.
// Output : Plain data
// return String(plain_data);
// "Gumi-note AES-CBC PKCS7 Padding Example."