概要
Arduino では開放状態のアナログ入力端子を読み取って乱数を生成するというのがしばしば行われるようです。 A/Dコンバータのノイズを拾って乱数を生成するのも電子工作ならではといった感じで面白そうですが、 Arduino R4 を持っているので、R4 ならではの機能を使って真乱数を生成してみようと思います。
Arduino R4 に搭載されている Renesas RA4M1 (R7FA4M1AB3CFM) には SCE5 という暗号化エンジンが搭載されており、TRNG (ハードウェア乱数生成器)が提供されているので、これを使ってみます。
SCE5 について
SCE5 は Renesas が開発した暗号化アクセラレータです。AESによる暗号化・復号と、128ビットの真乱数生成器 (TRNG) をサポートしています。
仕様は以下の通り。
- アクセス制御
- 暗号エンジン: AES
- キーサイズ: 128bit, 256bit
- 暗号利用モード
- ECB, CBC, CTR (NIST SP 800-38A 準拠)
- GCM (NIST SP 800-38D 準拠)
- XTS (NIST SP 800-38E 準拠)
- GCTR
- 認証付き暗号: AES-GCM
- キー管理
- 乱数の生成: 32ビットの真乱数生成器
- ユニークID
- 低消費電力: モジュールストップ状態の設定が可能
出典: https://www.renesas.com/jp/ja/document/mah/renesas-ra4m1-group-users-manual-hardware?r=1054146
AES も今度試してみたい。
使い方を調べる
マニュアルには上記の仕様やブロック図などが記載されているのですが詳しい使い方の説明はありません。 とりあえず SCE5 はデフォルトだと無効化されているので、有効化する必要があるということはマニュアルから読み取れました(それだけ・・・)。
ということで、マニュアルはそっ閉じしていくつか手掛かりになりそうなサイトを見てまわりました。
UNO R4 TRNG and AES / Arduino Forum
R4 の TRNG を動かすサンプルコードとかないの?みたいな内容の投稿。直接動かせるコードはないですが、参考になりそうなサイトやコードが載っているので、良いとっかかりとなりました。
HW_SCE_RNG_Read()
で 128bit 乱数を生成できそうですが、どのヘッダファイルを読み込めばいいのかまではわからない。
Github arduino/ArduinoCore-renesas - Hardware-Accelerator Crypto
この Issue を見て fsp_err_t HW_SCE_McuSpecificInit()
という関数があり、 SCE5 の初期化にはこれが必要ということがわかりました。返信に「libfsp.a のシンボルを確認してみたよ」って書いてる人がいたので、同じように nm で関係ありそうなシンボルを探して・・・ということをやっていました。
nm で確認したところ、以下のシンボルを見つけました。なんかいけそうです。
00000000 T HW_SCE_McuSpecificInit
00000000 T HW_SCE_RNG_Read
arduino/ArduinoCore-renesas リポジトリを見つつインクルードすべきヘッダファイルを探索。
HW_SCE_McuSpecificInit
は hw_sce_private.h
、 HW_SCE_RNG_Read
は hw_sce_trng_private.h
を読めば良さそう。
ちなみに、この Issue の主は SHA256 を計算したかったみたいですが、 SCE5 に SHA256 のハードウェア支援はないので、できないようです。
SCE5 モジュールのドライバとかライブラリの実装がここにあるので、こちらもたまに眺めにいきました。
ソースコード
フォーラムや Github の Issue で得た情報をベースに、 arduino/ArduinoCore-renesas のヘッダファイルを検索していき、最終的に動くものができました。
sce_trng.ino
#ifdef __cplusplus
extern "C" {
#endif
#include <hw_sce_private.h>
#include <hw_sce_trng_private.h>
#ifdef __cplusplus
}
#endif
uint32_t rnd[4] = { 0 };
fsp_err_t err = FSP_ERR_ASSERTION;
void setup() {
Serial.begin(115200);
while (!Serial)
;
Serial.println("PowerOn");
HW_SCE_PowerOn();
Serial.println("MCU Specific Init.");
err = HW_SCE_McuSpecificInit();
if (err != FSP_SUCCESS) {
Serial.println("MSU Specific Init failed!");
delay(5000);
} else {
Serial.println("MCU Specific Init done.");
Serial.println("SCE5 setup complete.");
}
}
void loop() {
err = HW_SCE_RNG_Read(rnd);
if (err != FSP_SUCCESS) {
Serial.print("failed HW_SCE_RNG_Read: ");
Serial.println(err, HEX);
} else {
char s[33] = {0};
sprintf(s, "%08X%08X%08X%08X", rnd[0], rnd[1], rnd[2], rnd[3]);
Serial.println(s);
}
delay(1000);
}
動作確認
動いていそうです。 1秒ごとに 128bit 乱数をシリアル通信でPCに送っています。
コンパイルできない場合の対処法
r_sce_if.h
のなかに C++ の予約語を使っている箇所があってコンパイルエラーになります。記載したソースコードでは直接読み込んでないですが、依存で読み込まれます。
とりあえず動かしたいので r_sce_if.h
を適当に書き換えました。
/* RSA 1024bit key index pair structure */
typedef struct sce_rsa1024_key_pair_index
{
sce_rsa1024_private_key_index_t private; // private を priv_key に書き換え
sce_rsa1024_public_key_index_t public; // public を pub_key に書き換え
} sce_rsa1024_key_pair_index_t;
このファイルには上記で定義されている構造体以外にも何箇所か public
, private
が使われているところがあるので、全て置き換えます。
MacOS の場合、 r_sce_if.h
は以下にあります。(MINIMAの場合)
/Users/{ユーザ名}/Library/Arduino15/packages/arduino/hardware/renesas_uno/1.1.0/variants/MINIMA/includes/ra/fsp/src/r_sce/crypto_procedures/src/sce5/plainkey/public/inc/r_sce_if.h
WiFi 版だったら以下の通り。
/Users/{ユーザ名}/Library/Arduino15/packages/arduino/hardware/renesas_uno/1.1.0/variants/UNOWIFIR4/includes/ra/fsp/src/r_sce/crypto_procedures/src/sce5/plainkey/public/inc/r_sce_if.h
SCE5 の TRNG の動かし方解説
大して理解できてないですが、わかる範囲で解説。
モジュールを有効化する(低消費電力モードの無効化)
SCE5 はデフォルトで無効化されているようなので、まずはモジュールを有効化する。
hw_sce_common.h
をインクルードして以下の関数を実行すれば良いです。
HW_SCE_PowerOn();
ちなみに HW_SCE_PowerOn()
は以下と同等の操作をしています。
R_MSTP->MSTPCRC_b.MSTPC31 = 0;
マニュアルによればモジュールストップコントロールレジスタC (MSTPCRC) というレジスタの b31
が SCE5 の有効化・無効化フラグになってるみたいです。
デフォルトでは 1
がセットされて無効化されているので、有効化のために 0
をセットしています。
SCE5 モジュールの初期化
HW_SCE_McuSpecificInit()
でモジュールの初期化をする必要があるみたいです。
内部では以下の処理を実行しているようですが、詳しいことは不明。初期化して自己診断を実行、みたいな感じ?
HW_SCE_SoftwareResetSub();
HW_SCE_SelfCheck1Sub();
HW_SCE_SelfCheck2Sub();
https://github.com/renesas/fsp に実装がありますがそれぞれの関数が何をしているかはさっぱりでした。
この初期化関数は fsp_err_t 型の返り値で成否を報告します。成功時は FSP_SUCCESS
、失敗時は FSP_ERR_CRYPTO_SCE_FAIL
, FSP_ERR_CRYPTO_SCE_RESOURCE_CONFLICT
, FSP_ERR_CRYPTO_SCE_RETRY
あたりが返るようです。
fsp_err_t は fsp_common_api.h に定義があります。
エラーについては FSP_ERR_CRYPTO_SCE_FAIL
は置いておいて、それ以外のエラーはリトライしておけば良さそう?(よくわかってないです)
乱数生成関数の呼び出し
HW_SCE_RNG_Read()
で 128bit 乱数を生成します。成功時に FSP_SUCCESS
が返るので、それ以外は失敗。これも多分リトライすればOK?
モジュールの初期化も乱数生成も失敗したことがまだないのでよくわかりません。
使用上の注意点
マニュアルには32ビットの真乱数生成器とありますが、 HW_SCE_RNG_Read()
関数は一度に128ビットの乱数を生成し、渡されたアドレスに書き込みます。
なので、この関数を使用する時は128ビット(32ビット x 4)分のメモリ領域を確保する必要があります。
指定した領域が不足しているとバッファオーバーフローが発生してプログラムの不正動作やセキュリティリスクを引き起こす原因となるため注意が必要です。
ダメな例:
uint32_t rnd32;
HW_SCE_RNG_Read(&rnd32); // 128ビットのデータが書き込まれるため、バッファオーバーフローが発生する。
OKな例:
uint32_t rnd[4] = {0}; // 128ビット分の領域を確保。
HW_SCE_RNG_Read(rnd);
速度、品質の評価
今回は速度や品質の評価を行っていませんが、今後検討してみます。 品質に関しては、暗号学に詳しくないため深い分析は難しいですが、NIST SP 800-22やDIEHARDなどの乱数検定を試してみるつもりです。