PSA APIs 04 · 鍵の作成と破棄 — import・generate・export・destroy

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 の鍵は、次の状態を通る。

  1. 作成: psa_import_key()(バイト列から)または psa_generate_key()(乱数から)、あるいは鍵導出(10 章)の出力として生まれる。鍵 ID が返る
  2. 使用: 鍵 ID を暗号・署名・導出の関数に渡す。何回でも
  3. (永続鍵のみ)保存: 作成時点で不揮発ストレージに書かれている。電源を切っても残る
  4. 破棄: psa_destroy_key() で値がメモリとストレージから消される。ID は無効になる
鍵の一生存在しないID は無効存在する鍵 ID で使える破棄済みID は無効import / generatederivation_output_keydestroy_key使用(何回でも)揮発鍵RAM のみ。プログラム終了・電源断でも消える永続鍵作成時点で ITS に書かれる。destroy するまで残る。同じ ID は ALREADY_EXISTS作成で鍵 ID が返り、破棄で無効になる。永続鍵は電源を切っても「存在する」に留まる
作成・使用・破棄の 3 段階。永続鍵だけが電源断をまたいで「存在する」状態を保つ

鍵の作成に関わる関数を一覧にする。

関数何をするか使う頻度
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 に預ける関数である。 工場で書き込まれた秘密、サーバから受け取った公開鍵、設定ファイルから読んだ共有鍵、などがこれに当たる。

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 ∥ Y1 + 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_PAIRPKCS#1(RSA 鍵の標準形式)の RSAPrivateKey を DER(Distinguished Encoding Rules。ASN.1 で定義した構造をバイナリにする規則)符号化したもの可変(2048 ビットで約 1190 バイト)
RSA_PUBLIC_KEYPKCS#1 の RSAPublicKey を DER 符号化したもの(SubjectPublicKeyInfo ではない)可変(2048 ビットで 270 バイト)

とくに注意する点:

psa_import_key / psa_export_* のバイト列形式ECC 公開鍵(P-256): 非圧縮点形式 65 バイト0x041X 座標(32 バイト、ビッグエンディアン)32Y 座標(32 バイト)32ECC 秘密鍵(P-256 鍵ペア): 整数 d をビッグエンディアン固定長でd(32 バイト。先頭 0 詰め)32ECDSA 署名(9 章): r と s の生の連結。DER ではないr(32 バイト)32s(32 バイト)32受け付けない形式PEM(-----BEGIN …)、SubjectPublicKeyInfo の DER 包装、SEC1 ECPrivateKey の包装、DER 形式の ECDSA 署名。いずれも中身のバイト列を取り出してから渡すRSA: PKCS#1 の RSAPrivateKey / RSAPublicKey を DER 符号化(包装なし)。X25519 / Ed25519: 32 バイト(RFC 7748 / 8032)形式が違うと PSA_ERROR_INVALID_ARGUMENT。import で最も多いエラー
PSA は「包装のない生のバイト列」を扱う。証明書や PEM から取り出すのは PSA の外の仕事

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));

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 バイト)。

正当な使い道は限られる。

「とりあえず EXPORT を付けておく」は、PSA を使う意味を消す行為である。 設計レビューでは、EXPORT を持つ鍵をすべて列挙し、1 つずつ理由を説明できるようにしておく。

6. psa_destroy_key() — 消す

psa_status_t psa_destroy_key(psa_key_id_t key);

分割操作(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 のどちらが返るかが違う。両方を受けるのが安全である。

永続鍵は「あれば使う、なければ作る」psa_get_key_attributes(ID)存在を確かめるSUCCESSすでにある → その ID を使うDOES_NOT_EXIST / INVALID_HANDLE初回 → generate_key で作る以後は ID を直接使えるpsa_sign_hash(ID, ...)毎回 generate すると2 回目以降は PSA_ERROR_ALREADY_EXISTS で失敗する。存在しない永続鍵に対して 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 を直接使えばよい—

この章のポイント