Chapter 04
鍵の作成と破棄 — import・generate・export・destroy
この章のゴール.
鍵を外から取り込む(import)、中で作る(generate)、 公開鍵を取り出す(export_public_key)、消す(destroy)の 4 つを、 鍵のバイト列の形式まで含めて正確に使えるようになること。
この章で使う既出の用語(定義は各リンク先). PSA(01 章 1 節)、ビット(02 章 4 節)、algorithm(03 章 11 節)、type(03 章 11 節)、usage(03 章 11 節)、持続性(03 章 7 節)
1. 鍵の一生
PSA の鍵は、次の状態を通る。
- 作成:
psa_import_key()(バイト列から)またはpsa_generate_key()(乱数から)、あるいは鍵導出(10 章)の出力として生まれる。鍵 ID が返る - 使用: 鍵 ID を暗号・署名・導出の関数に渡す。何回でも
- (永続鍵のみ)保存: 作成時点で不揮発ストレージに書かれている。電源を切っても残る
- 破棄:
psa_destroy_key()で値がメモリとストレージから消される。ID は無効になる
鍵の作成に関わる関数を一覧にする。
| 関数 | 何をするか | 使う頻度 |
|---|---|---|
psa_import_key(attr, data, len, &key) | バイト列を鍵として取り込む | 高 |
psa_generate_key(attr, &key) | 乱数で鍵を作る(対称鍵、ECC/RSA 鍵ペア) | 高 |
psa_destroy_key(key) | 鍵を消す | 高 |
psa_export_public_key(key, buf, size, &len) | 鍵ペアから公開鍵を取り出す | 高 |
psa_export_key(key, buf, size, &len) | 鍵の値を取り出す(EXPORT 用途が必要) | 中 |
psa_get_key_attributes(key, &attr) | 属性を読む | 中 |
psa_copy_key(src, attr, &dst) | 別の属性・寿命で複製する(COPY 用途が必要) | 低 |
psa_purge_key(key) | 永続鍵のメモリ上のコピーだけ捨てる(ストレージには残る) | 低 |
psa_key_derivation_output_key() | 導出で鍵を作る(10 章) | 中 |
2. psa_import_key() — バイト列を鍵にする
psa_status_t psa_import_key(const psa_key_attributes_t *attributes,
const uint8_t *data, size_t data_length,
psa_key_id_t *key);「よそで作られた鍵」を PSA に預ける関数である。 工場で書き込まれた秘密、サーバから受け取った公開鍵、設定ファイルから読んだ共有鍵、などがこれに当たる。
attributesの type と usage と algorithm は必須。bits は省略可(データから決まる)- 成功すると
*keyに鍵 ID が入る。揮発鍵なら実装が ID を割り当て、永続鍵なら属性で指定した ID がそのまま入る dataは取り込み後にアプリケーション側で消す(memsetで 0 埋め)。PSA はコピーを持っているので、元は不要である- 永続鍵で同じ ID がすでにあると
PSA_ERROR_ALREADY_EXISTS。上書きはしない
static const uint8_t aes_key[16] = { 0x2b,0x7e,0x15,0x16, 0x28,0xae,0xd2,0xa6,
0xab,0xf7,0x15,0x88, 0x09,0xcf,0x4f,0x3c };
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_key_id_t key = PSA_KEY_ID_NULL;
psa_set_key_type(&attr, PSA_KEY_TYPE_AES);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);
psa_set_key_algorithm(&attr, PSA_ALG_GCM);
PSA_CHECK(psa_import_key(&attr, aes_key, sizeof aes_key, &key));
psa_reset_key_attributes(&attr);
/* key は 128 ビット AES-GCM 鍵として使える */2.1 鍵のバイト列の形式
インポートするバイト列は、鍵の種類ごとに決まった形式でなければならない。 形式が違うと PSA_ERROR_INVALID_ARGUMENT になる。これが import で最も多いエラーである。
| 鍵の種類 | 形式 | 長さ |
|---|---|---|
| AES、ChaCha20、HMAC、DERIVE、RAW_DATA | 生のバイト列 | 鍵長そのまま |
ECC_KEY_PAIR(Weierstrass 曲線——P-256 などの、教科書的な形の楕円曲線) | 秘密鍵の整数 d をビッグエンディアンで。先頭 0 詰めで固定長 | ceil(bits/8)(P-256 で 32、P-521 で 66) |
ECC_PUBLIC_KEY(Weierstrass) | 非圧縮点形式(曲線上の点の X と Y をそのまま並べる形式): 0x04 ∥ X ∥ Y | 1 + 2·ceil(bits/8)(P-256 で 65) |
ECC_KEY_PAIR(Montgomery——X25519 が使う、鍵合意向きの形の曲線) | 秘密スカラー(曲線上の点に掛ける整数)32 バイト(RFC 7748——X25519 を定めたインターネット標準文書——の形式) | 32(Curve448 は 56) |
ECC_PUBLIC_KEY(Montgomery) | u 座標のみ、リトルエンディアン | 32 |
ECC_KEY_PAIR(Twisted Edwards——Ed25519 が使う、署名向きの形の曲線) | 秘密シード 32 バイト(RFC 8032) | 32 |
ECC_PUBLIC_KEY(Twisted Edwards) | 圧縮点 32 バイト(RFC 8032) | 32 |
RSA_KEY_PAIR | PKCS#1(RSA 鍵の標準形式)の RSAPrivateKey を DER(Distinguished Encoding Rules。ASN.1 で定義した構造をバイナリにする規則)符号化したもの | 可変(2048 ビットで約 1190 バイト) |
RSA_PUBLIC_KEY | PKCS#1 の RSAPublicKey を DER 符号化したもの(SubjectPublicKeyInfo ではない) | 可変(2048 ビットで 270 バイト) |
とくに注意する点:
- PEM は受け付けない。PEM とは DER を Base64(バイナリを英数字の文字列にする符号化)にして
-----BEGIN ...-----で挟んだテキスト形式で、ヘッダを外して Base64 を戻し、DER にしてから渡す。Mbed TLS ではmbedtls_pk_parse_key()で読んでから PSA に移す(15 章) - ECC 公開鍵は 65 バイトの
04 ∥ X ∥ Y。X.509(公開鍵証明書の標準形式)証明書から取り出す場合、SubjectPublicKeyInfo(証明書内で公開鍵を包む構造)の中の BIT STRING(DER のビット列型)の中身がこの 65 バイトである。DER の外側の包装(OID——アルゴリズムを示す番号——など)は含めない - ECDSA の秘密鍵を DER(SEC1——楕円曲線鍵の形式を定めた規格——の
ECPrivateKey)で持っているなら、その中のprivateKeyOCTET STRING(DER のバイト列型)の 32 バイトだけを渡す
3. psa_generate_key() — 乱数から鍵を作る
psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
psa_key_id_t *key);「この装置だけが持つ秘密」を作るときの関数である。 デバイスの署名鍵は、必ず装置内で生成して、外に出さない——これが PSA の想定する使い方である。 attributes の bits は必須(何ビットの鍵を作るか分からないと生成できない)。
psa_set_key_type(&attr, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
psa_set_key_bits(&attr, 256);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH);
psa_set_key_algorithm(&attr, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_id(&attr, KEY_ID_DEVICE_SIGN);
PSA_CHECK(psa_generate_key(&attr, &key));- 対称鍵(AES、HMAC、DERIVE など)の生成は、内部的には
psa_generate_random()と同じ乱数で埋めるだけである - ECC 鍵ペアの生成は P-256 で数十 ms(ソフトウェア、Cortex-M4 100 MHz)。RSA-2048 は数秒〜数十秒かかり、しかも時間がばらつく(素数探索のため)
- 生成が失敗する典型は
PSA_ERROR_INSUFFICIENT_ENTROPY(乱数源が未設定)とPSA_ERROR_NOT_SUPPORTED(その type の生成がビルドで無効。Mbed TLS ではPSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATEが必要。15 章)
Crypto API 1.2 以降には、生成時に追加パラメータ(RSA の公開指数など)を渡す psa_generate_key_custom() があるが、通常は使わない。
4. psa_export_public_key() — 公開鍵を相手に渡す
psa_status_t psa_export_public_key(psa_key_id_t key,
uint8_t *data, size_t data_size, size_t *data_length);鍵ペアから公開鍵部分だけを取り出す。用途フラグは関係なく、鍵ペアか公開鍵の種類であれば常に成功する。 出力形式は 2.1 節の ECC_PUBLIC_KEY / RSA_PUBLIC_KEY の形式と同じである。
uint8_t pub[PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1), 256)];
size_t pub_len;
PSA_CHECK(psa_export_public_key(key, pub, sizeof pub, &pub_len));
/* pub[0]==0x04, pub_len==65。これをサーバに登録する */出力サイズは PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(type, bits) で計算する。 type には鍵ペアの type を渡してよい(マクロが公開鍵の形式に読み替える)。 アルゴリズムを実行時に決めるなら PSA_EXPORT_PUBLIC_KEY_MAX_SIZE を使う。
公開鍵を「証明書」にするのは PSA の仕事ではない.
psa_export_public_key()が返すのは、X.509 証明書ではなく生の公開鍵である。 証明書署名要求(CSR)を作ったり、証明書を解析したりするのは Mbed TLS の X.509 モジュール(mbedtls_x509_*)の仕事で、 PSA Crypto API の範囲外である。PSA は「鍵と計算」だけを扱う。
5. psa_export_key() — 鍵の値を取り出す(使わないのが理想)
psa_status_t psa_export_key(psa_key_id_t key,
uint8_t *data, size_t data_size, size_t *data_length);鍵の値そのものを取り出す。属性に PSA_KEY_USAGE_EXPORT がないと PSA_ERROR_NOT_PERMITTED。 形式は 2.1 節の表のとおり(ECC 鍵ペアなら秘密鍵 d の 32 バイト)。
正当な使い道は限られる。
- 装置間で共有する対称鍵を生成して、別の鍵で包んで(暗号化して)相手に送る
- テストで既知の答え(テストベクタ)と比較する
- PSA を使わない古いライブラリに鍵を渡す(移行期間)
「とりあえず EXPORT を付けておく」は、PSA を使う意味を消す行為である。 設計レビューでは、EXPORT を持つ鍵をすべて列挙し、1 つずつ理由を説明できるようにしておく。
6. psa_destroy_key() — 消す
psa_status_t psa_destroy_key(psa_key_id_t key);- 揮発鍵: メモリから消去(実装は 0 埋めする)。ID は無効になる
- 永続鍵: 不揮発ストレージからも消す。次回起動でも存在しない
PSA_KEY_ID_NULLを渡すとPSA_SUCCESS(何もしない)。なので cleanup で無条件に呼んでよい- 破棄した ID を再度使うと
PSA_ERROR_INVALID_HANDLE READ_ONLY持続性の鍵(工場出荷鍵など)は破棄できずPSA_ERROR_NOT_PERMITTED
分割操作(operation)が使用中の鍵を破棄するとどうなるか——仕様では「進行中の操作は失敗するかもしれないし、続くかもしれない」であり、実装依存である。 操作を abort してから鍵を消す順序を守る。
永続鍵を作るコードは「あれば使う、なければ作る」で書く.
起動のたびに
psa_generate_key()を呼ぶと、2 回目以降はPSA_ERROR_ALREADY_EXISTSで失敗する。 正しい形は、まずpsa_get_key_attributes()で存在を確かめ、なければ作る。psa_key_attributes_t a = PSA_KEY_ATTRIBUTES_INIT; st = psa_get_key_attributes(KEY_ID_DEVICE_SIGN, &a); psa_reset_key_attributes(&a); if (st == PSA_ERROR_DOES_NOT_EXIST || st == PSA_ERROR_INVALID_HANDLE) { st = generate_device_key(); /* 初回だけ */ } key = KEY_ID_DEVICE_SIGN; /* 永続鍵は ID を直接使える */存在しない永続鍵の ID に対して、実装によって
DOES_NOT_EXISTとINVALID_HANDLEのどちらが返るかが違う。両方を受けるのが安全である。
7. psa_copy_key() と psa_purge_key() — 低頻度だが知っておく
psa_copy_key(source, attributes, &target) は、鍵を別の属性で複製する。 典型的な用途は「揮発鍵として導出した鍵を、永続鍵として保存し直す」「用途を狭めた複製を別モジュールに渡す」である。 元の鍵に PSA_KEY_USAGE_COPY が必要で、複製の用途は元の用途の部分集合でなければならない(広げることはできない)。
psa_purge_key(key) は、永続鍵のメモリ上のキャッシュだけを捨てる。 ストレージには残っているので、次に使うときに再び読み込まれる。 PSA_KEY_USAGE_CACHE を付けた鍵はメモリに残り続けるので、明示的に追い出したいときに呼ぶ。 メモリの厳しいマイコンで、使用頻度の低い大きな RSA 鍵を持つ場合に意味がある。
8. 鍵を動かす練習
鍵の一生を追う
9. 仕様書に逃がす
次の関数は存在を知っておけば十分である。必要になったら仕様書の該当節を引く。
| 関数 | 用途 | 仕様書の節 |
|---|---|---|
psa_generate_key_custom() / psa_generate_key_ext() | RSA 公開指数などの生成パラメータを指定 | Key management → Key creation |
psa_set_key_enrollment_algorithm() | 2 つ目の許可アルゴリズム(実装拡張) | 同上(Mbed TLS 拡張) |
psa_key_derivation_output_key_custom() | 導出時のパラメータ指定 | Key derivation |
psa_open_key() / psa_close_key() | 廃止された旧 API。永続鍵は ID を直接使えばよい | — |
この章のポイント
- import は外から、generate は乱数から。デバイス固有の秘密鍵は generate で装置内に閉じ込める
- import するバイト列の形式は種類ごとに決まっている。ECC 公開鍵は
04 ∥ X ∥ Yの 65 バイト、PEM は不可 - 公開鍵はいつでも
psa_export_public_key()。秘密鍵はEXPORT用途がなければ出せない - 永続鍵は「あれば使う、なければ作る」。毎回 generate すると
ALREADY_EXISTS - cleanup では
abort→destroyの順。PSA_KEY_ID_NULLの destroy は安全