通信には暗号化が必須
とは言ってみましたが、よほどの変人でない限り通信プロトコルを覗いてみようなんて考える人はまずいないのではないかと思います(笑)。だって、WiFiの電波を傍受して中身を見てみようって思ったことありますか?まだWiFiの暗号化がWEPしか存在していなかった時代ですら、傍受してデコードして中身を見てみようなんて考えたことないですよ・・・。
ただ、やっぱりそういうことが好きな人はいますし、重要なシステムだとそんなに簡単に破られるわけにはいきません。特にお金絡みでそういうことがあると信用そのものがなくなってしまいます。だからこそ常に暗号化は進化し続けているし、存在しているのです。
ボンディングすればチップが勝手にやってくれる
Bluetoothにおいてはボンディング(Classicだとペアリング)すればハードウェアレベルでの暗号化が勝手に行われます。スニファーで覗いてみても暗号化されているのが分かります。じゃあそれを使えばいいじゃん、ということになるのですが、まあそれでよければそれでも全然問題ないです。
ただ、使う前にボンディングをしないといけないというのはとても使い勝手が悪くなります。特にBluetooth LEにおいては異なる機器との接続をした場合はボンディング情報を何らかの手順で消去してやるケース(割愛します)もあって非常に煩雑になる可能性があります。BLEのメリットの一つとして「ボンディングしなくてもP2P通信ができる」というのがあるのに本末転倒じゃないですか。
自前で暗号化する
ということでボンディングしなくても使えて、かつ通信の秘匿性を高めるために取りうる手段としては「自前で暗号化」以外ありません。
一通りのサンプルは揃っている
実は暗号化については一通りのサンプルは揃っています。えっ?見つからない?Project選択のところにあるOther Nordic Projectsのチェックボックスをチェックすると出てきますよ。
今回はAES CTRを実装します。
えっ、なぜ今時AES CTRなのかって?実際に暗号化を実装したのはnRF SDKですが、AndroidもiOSもライブラリが用意されていて、かつnRF52側もハードウェアエンコードができる暗号化がAES CTR(の128ビット)だったからです。nRF Connect SDKにおいては基本的な流れは全部一緒のようなので、どれか一つ理解すればあとはパラメーターを変えるだけでよいので楽だと思います。
プロジェクトを読み込む
今まではなんだかんだで自前で書き直していましたが、今回は初めて手抜き(笑)でサンプルプロジェクトをそのまま使おうと思います。
コンパイルして走らせてみると・・・
ちゃんとエンコード、デコードできているようです。
キーがランダム・・・だと・・・?
暗号化をかじったことがある人は分かると思いますが、AESの暗号化をするためにはIV(Initialization Vector)とKeyの2つのソルトが必要です。当たり前の話ですが、このIVとKeyはソフトウェア(アプリ)側も知っていないとデコードすることができません。両方とも埋め込みでもかまわないのですが、いずれにしても何らかの形で知っている必要があります。
ソースコードを追っていくと、IVについては乱数で生成させたものをセットしているのが分かります。これだったらこのAPIを使わずに自分でセットした文字列を代わりにセットしてやればよさそうです。残るもう一つのKeyですが・・・なんと!KeyについてはいきなりGenerateコマンドで常に乱数で内部生成する方式のようです。デバッガーで覗いてみましたが、格納している場所は分かりませんでした。
いや、そんなはずはないでしょ・・・
常に乱数で生成するだけならそれをアプリ側に通信で伝えてやることで何とか通信の暗号化として使えますが、内部に隠ぺいする形で生成しているのではさすがに使えません。だってアプリ側はデコードキーがないわけですから。
いや、でもさすがにそんなはずはないでしょ・・・と思って他のプロジェクトも色々と調べていくと、Generate関数以外にImport関数があるというのを発見しました。これを使えば乱数ではないキーを指定できるようです。ふぅ。
改版後のソースコード
いくらサンプルプロジェクトでもこれくらいのレベルのサンプルであってほしい(笑)
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
# include <zephyr.h>
# include <sys/printk.h>
# include <logging/log.h>
# include <stdio.h>
# include <psa/crypto.h>
# include <psa/crypto_extra.h>
# ifdef CONFIG_BUILD_WITH_TFM
# include <tfm_ns_interface.h>
# endif
# define APP_SUCCESS (0)
# define APP_ERROR (-1)
# define APP_SUCCESS_MESSAGE "Example finished successfully!"
# define APP_ERROR_MESSAGE "Example exited with error!"
# define USE_FIX_KEYS (0)
# define PRINT_HEX(p_label, p_text, len)\
({\
LOG_INF("---- %s (len: %u): ----", p_label, len);\
LOG_HEXDUMP_INF(p_text, len, "Content:");\
LOG_INF("---- %s end ----", p_label);\
})
LOG_MODULE_REGISTER(aes_ctr, LOG_LEVEL_DBG);
/* ====================================================================== */
/* Global variables/defines for the AES CTR example */
# define NRF_CRYPTO_EXAMPLE_AES_MAX_TEXT_SIZE (64)
# define NRF_CRYPTO_EXAMPLE_AES_BLOCK_SIZE (16)
/* AES IV buffer */
static uint8_t m_iv[NRF_CRYPTO_EXAMPLE_AES_BLOCK_SIZE] = {
"test_aes_ctr"
};
static uint8_t m_key[NRF_CRYPTO_EXAMPLE_AES_BLOCK_SIZE] = {
"test_aes_ctr"
};
/* Below text is used as plaintext for encryption/decryption */
static uint8_t m_plain_text[NRF_CRYPTO_EXAMPLE_AES_MAX_TEXT_SIZE] = {
"Example string to demonstrate basic usage of AES CTR mode."
};
static uint8_t m_encrypted_text[NRF_CRYPTO_EXAMPLE_AES_MAX_TEXT_SIZE];
static uint8_t m_decrypted_text[NRF_CRYPTO_EXAMPLE_AES_MAX_TEXT_SIZE];
static psa_key_handle_t key_handle;
/* ====================================================================== */
int crypto_init(void)
{
psa_status_t status;
/* Initialize PSA Crypto */
status = psa_crypto_init();
if (status != PSA_SUCCESS)
{
return APP_ERROR;
}
return APP_SUCCESS;
}
int crypto_finish(void)
{
psa_status_t status;
/* Destroy the key handle */
status = psa_destroy_key(key_handle);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_destroy_key failed! (Error: %d)", status);
return APP_ERROR;
}
return APP_SUCCESS;
}
int generate_key(void)
{
psa_status_t status;
LOG_INF("Generating random AES key...");
/* Configure the key attributes */
psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);
psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE);
psa_set_key_algorithm(&key_attributes, PSA_ALG_CTR);
psa_set_key_type(&key_attributes, PSA_KEY_TYPE_AES);
psa_set_key_bits(&key_attributes, 128);
/* Generate a random key. The key is not exposed to the application,
* we can use it to encrypt/decrypt using the key handle
*/
status = psa_generate_key(&key_attributes, &key_handle);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_generate_key failed! (Error: %d)", status);
return APP_ERROR;
}
/* After the key handle is acquired the attributes are not needed */
psa_reset_key_attributes(&key_attributes);
LOG_INF("AES key generated successfully!");
return APP_SUCCESS;
}
int import_key(void)
{
/* Configure the key attributes */
psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_status_t status;
/* Configure the key attributes */
psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);
psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE);
psa_set_key_algorithm(&key_attributes, PSA_ALG_CTR);
psa_set_key_type(&key_attributes, PSA_KEY_TYPE_AES);
psa_set_key_bits(&key_attributes, 128);
status = psa_import_key(&key_attributes, m_key, sizeof(m_key), &key_handle);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_import_key failed! (Error: %d)", status);
return APP_ERROR;
}
/* After the key handle is acquired the attributes are not needed */
psa_reset_key_attributes(&key_attributes);
LOG_INF("AES key imported successfully!");
return APP_SUCCESS;
}
int encrypt_ctr_aes(void)
{
uint32_t olen;
psa_status_t status;
psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT;
LOG_INF("Encrypting using AES CTR MODE...");
/* Setup the encryption operation */
status = psa_cipher_encrypt_setup(&operation, key_handle, PSA_ALG_CTR);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_encrypt_setup failed! (Error: %d)", status);
return APP_ERROR;
}
# if (USE_FIX_KEYS)
/* Set the IV to the one generated during encryption */
status = psa_cipher_set_iv(&operation, m_iv, sizeof(m_iv));
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_set_iv failed! (Error: %d)", status);
return APP_ERROR;
}
# else
/* Generate a random IV */
status = psa_cipher_generate_iv(&operation, m_iv, sizeof(m_iv),
&olen);
if (status != PSA_SUCCESS) {
LOG_INF("psa_cipher_generate_iv failed! (Error: %d)", status);
return APP_ERROR;
}
# endif
/* Perform the encryption */
status = psa_cipher_update(&operation, m_plain_text, sizeof(m_plain_text), m_encrypted_text, sizeof(m_encrypted_text), &olen);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_update failed! (Error: %d)", status);
return APP_ERROR;
}
/* Finalize encryption */
status = psa_cipher_finish(&operation, m_encrypted_text + olen, sizeof(m_encrypted_text) - olen, &olen);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_finish failed! (Error: %d)", status);
return APP_ERROR;
}
/* Clean up cipher operation context */
status = psa_cipher_abort(&operation);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_abort failed! (Error: %d)", status);
return APP_ERROR;
}
LOG_INF("Encryption successful!\r\n");
PRINT_HEX("IV", m_iv, sizeof(m_iv));
PRINT_HEX("Encrypted text", m_encrypted_text, sizeof(m_encrypted_text));
return APP_SUCCESS;
}
int decrypt_ctr_aes(void)
{
uint32_t olen;
psa_status_t status;
psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT;
LOG_INF("Decrypting using AES CTR MODE...");
/* Setup the decryption operation */
status = psa_cipher_decrypt_setup(&operation, key_handle, PSA_ALG_CTR);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_decrypt_setup failed! (Error: %d)", status);
return APP_ERROR;
}
/* Set the IV to the one generated during encryption */
status = psa_cipher_set_iv(&operation, m_iv, sizeof(m_iv));
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_set_iv failed! (Error: %d)", status);
return APP_ERROR;
}
/* Perform the decryption */
status = psa_cipher_update(&operation, m_encrypted_text, sizeof(m_encrypted_text), m_decrypted_text, sizeof(m_decrypted_text), &olen);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_update failed! (Error: %d)", status);
return APP_ERROR;
}
/* Finalize the decryption */
status = psa_cipher_finish(&operation, m_decrypted_text + olen, sizeof(m_decrypted_text) - olen, &olen);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_finish failed! (Error: %d)", status);
return APP_ERROR;
}
PRINT_HEX("Decrypted text", m_decrypted_text, sizeof(m_decrypted_text));
/* Check the validity of the decryption */
if (memcmp(m_decrypted_text, m_plain_text, NRF_CRYPTO_EXAMPLE_AES_MAX_TEXT_SIZE) != 0)
{
LOG_INF("Error: Decrypted text doesn't match the plaintext");
return APP_ERROR;
}
/* Clean up cipher operation context */
status = psa_cipher_abort(&operation);
if (status != PSA_SUCCESS)
{
LOG_INF("psa_cipher_abort failed! (Error: %d)", status);
return APP_ERROR;
}
LOG_INF("Decryption successful!");
return APP_SUCCESS;
}
int main(void)
{
int status;
LOG_INF("Starting AES CTR example...");
status = crypto_init();
if (status != APP_SUCCESS)
{
LOG_INF(APP_ERROR_MESSAGE);
return APP_ERROR;
}
# if (USE_FIX_KEYS)
status = import_key();
if (status != APP_SUCCESS)
{
LOG_INF(APP_ERROR_MESSAGE);
return APP_ERROR;
}
# else
status = generate_key();
if (status != APP_SUCCESS)
{
LOG_INF(APP_ERROR_MESSAGE);
return APP_ERROR;
}
# endif
status = encrypt_ctr_aes();
if (status != APP_SUCCESS)
{
LOG_INF(APP_ERROR_MESSAGE);
return APP_ERROR;
}
status = decrypt_ctr_aes();
if (status != APP_SUCCESS)
{
LOG_INF(APP_ERROR_MESSAGE);
return APP_ERROR;
}
status = crypto_finish();
if (status != APP_SUCCESS)
{
LOG_INF(APP_ERROR_MESSAGE);
return APP_ERROR;
}
LOG_INF(APP_SUCCESS_MESSAGE);
return APP_SUCCESS;
}
prj.confはサンプルと一緒なので省略します。
USE_FIX_KEYSのdefineを0にすると乱数で生成したIVとKeyを使い、1にすると自前で用意したIVとKey(このサンプルでは両方とも"test_aes_ctr"です)を使用します。
nRF SDKと同じ結果を生成するかは試していない
すみません、めんどくさいので現時点ではnRF SDKと同じ結果になるかは試しておりません・・・。近いうちに確認してみる予定です。