PSA APIs 03 · 鍵と属性 — 「何の鍵で、何に使えるか」を宣言する

Chapter 03

鍵と属性 — 「何の鍵で、何に使えるか」を宣言する

この章のゴール.

psa_key_attributes_t の 6 つの項目(種類・長さ・用途・アルゴリズム・寿命・ID)を、 それぞれ何を守るために存在するのかまで含めて説明でき、 目的に応じた属性の組み合わせを自分で書けるようになること。

この章で使う既出の用語(定義は各リンク先). PSA(01 章 1 節)、ビット(02 章 4 節)

1. 鍵は「値」ではなく「値+方針」である

従来の暗号ライブラリでは、鍵は 16 バイトや 32 バイトのただのバイト列だった。 そのバイト列を AES に渡せば AES 鍵として、HMAC に渡せば HMAC 鍵として使えてしまう。 「同じ鍵を 2 つの目的に使う」のは暗号設計の禁じ手であるが、ライブラリはそれを止められなかった。

PSA では、鍵は値と方針(policy)のセットとして管理される。 鍵を作るときに「これは AES 鍵で、CTR モードの暗号化・復号にだけ使う」と宣言し、 以後、それ以外の使い方は PSA_ERROR_NOT_PERMITTED で拒否される。 この宣言を書き込む構造体が psa_key_attributes_t(鍵属性)である。

鍵 = 値 + 方針(policy)従来のライブラリ鍵 = 16 バイトの配列uint8_t key[16]AES に渡すHMAC にも渡せる同じ鍵の使い回しを止められないPSA鍵 = 値 + psa_key_attributes_ttype: AES / bits: 128 / usage: ENCRYPT|DECRYPT / algorithm: GCM / lifetime: 揮発 / id: —AES-GCM: 可HMAC: NOT_PERMITTED宣言した方針以外はライブラリが拒否する
PSA では鍵を作るときに「何の鍵で、何に使えるか」を宣言し、以後ライブラリがそれを強制する

2. 属性構造体の使い方

属性は不透明な構造体(中身を直接触ってはいけない構造体)で、 必ず PSA_KEY_ATTRIBUTES_INIT で初期化し、setter 関数で項目を設定する。

psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;

psa_set_key_type(&attr, PSA_KEY_TYPE_AES);                 /* ① 種類 */
psa_set_key_bits(&attr, 256);                              /* ② 長さ(ビット) */
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);  /* ③ 用途 */
psa_set_key_algorithm(&attr, PSA_ALG_GCM);                 /* ④ 許すアルゴリズム */
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_VOLATILE);    /* ⑤ 寿命(省略時は揮発) */
/* psa_set_key_id(&attr, ...);  ⑥ ID(永続鍵のときだけ) */

psa_status_t st = psa_generate_key(&attr, &key);
psa_reset_key_attributes(&attr);   /* 使い終わったら片付ける */

psa_reset_key_attributes() は、属性構造体が内部で確保したメモリがあれば解放し、初期状態に戻す。 現在の実装ではほとんど何もしないが、将来の拡張で属性が動的メモリを持つ可能性に備えて仕様が要求している。 psa_get_key_attributes()(既存の鍵から属性を読み出す。04 章)で得た構造体には特に必須である。

6 つの項目を 1 つずつ見る。

3. 種類 psa_key_type_t — その鍵はどの暗号のものか

鍵の種類は、鍵の値のバイト列をどう解釈するかを決める。 AES 鍵はただの 16/24/32 バイトだが、楕円曲線の秘密鍵は「ある曲線上の整数」であり、 RSA 鍵は「複数の大きな整数の組」である。

定数値意味鍵長(ビット)
PSA_KEY_TYPE_AES0x2400AES ブロック暗号の鍵128 / 192 / 256
PSA_KEY_TYPE_CHACHA200x2004ChaCha20 ストリーム暗号の鍵256
PSA_KEY_TYPE_HMAC0x1100HMAC の鍵任意(ハッシュのブロック長以下が普通)
PSA_KEY_TYPE_DERIVE0x1200鍵導出の入力(マスター秘密)。それ以外に使えない任意
PSA_KEY_TYPE_RAW_DATA0x1001暗号には使えない生データ。ストレージのように扱うためのもの任意
PSA_KEY_TYPE_PASSWORD0x1203パスワード(PBKDF2 の入力)任意
PSA_KEY_TYPE_ECC_KEY_PAIR(family)0x7100 ∣ family楕円曲線の鍵ペア(秘密鍵。公開鍵は導出できる)曲線による(256 など)
PSA_KEY_TYPE_ECC_PUBLIC_KEY(family)0x4100 ∣ family楕円曲線の公開鍵だけ同上
PSA_KEY_TYPE_RSA_KEY_PAIR0x7001RSA 鍵ペア2048 / 3072 / 4096
PSA_KEY_TYPE_RSA_PUBLIC_KEY0x4001RSA 公開鍵同上

楕円曲線の family(族)は、曲線の種類を表す 8 ビット値である。

family曲線使う場面
PSA_ECC_FAMILY_SECP_R1 (0x12)NIST P-256 / P-384 / P-521ECDSA 署名、ECDH。最も一般的。鍵長で曲線を選ぶ(256 → P-256)
PSA_ECC_FAMILY_MONTGOMERY (0x41)Curve25519 (255 ビット) / Curve448X25519 鍵合意
PSA_ECC_FAMILY_TWISTED_EDWARDS (0x42)Ed25519 (255 ビット) / Ed448EdDSA 署名
PSA_ECC_FAMILY_SECP_K1 (0x17)secp256k1ブロックチェーン系
PSA_ECC_FAMILY_BRAINPOOL_P_R1 (0x30)Brainpool欧州の規格

「鍵ペア」と「公開鍵」を別の種類にしている点に注目してほしい。 相手から受け取った公開鍵を psa_import_key() するときは PSA_KEY_TYPE_ECC_PUBLIC_KEY(...)、 自分の秘密鍵を生成するときは PSA_KEY_TYPE_ECC_KEY_PAIR(...) である。 鍵ペアからは psa_export_public_key() で公開鍵部分だけを取り出せる(04 章)。

psa_key_type_t(16 ビット)の主な値カテゴリ上位ビット: 0x1 生/HMAC, 0x2 対称, 0x4 公開鍵, 0x7 鍵ペア詳細暗号の種類、または楕円曲線の族(family)対称鍵・生データAES 0x2400128 / 192 / 256 ビットHMAC 0x1100MAC 用。長さ任意DERIVE 0x1200鍵導出の入力専用RAW_DATA 0x1001暗号に使えない生データ楕円曲線(family を添える)ECC_KEY_PAIR(f) 0x7100|f秘密鍵。公開鍵を導出できるECC_PUBLIC_KEY(f) 0x4100|f公開鍵だけSECP_R1 f=0x12P-256 / P-384 / P-521MONTGOMERY f=0x41X25519(255 ビット)RSARSA_KEY_PAIR 0x70012048 以上RSA_PUBLIC_KEY 0x4001公開鍵だけTWISTED_EDWARDS f=0x42Ed25519(255 ビット)CHACHA20 0x2004256 ビット。AEAD 用
鍵ペア(0x7…)と公開鍵(0x4…)は別の種類。相手の公開鍵を取り込むときは PUBLIC_KEY を使う

4. 長さ bits — 生成のときに必要、インポートのときは自動

鍵長はビット単位で指定する。 psa_generate_key() で鍵を作るときは必須で、 psa_import_key() でバイト列から取り込むときは省略できる(バイト列の長さから決まる。指定した場合は一致しないと PSA_ERROR_INVALID_ARGUMENT)。

注意すべき鍵長:

5. 用途 psa_key_usage_t — 何をしてよいか

用途フラグは、その鍵で呼んでよい操作を 1 ビットずつ表す。 複数を OR で組み合わせる。

フラグ値許す操作
PSA_KEY_USAGE_ENCRYPT0x0100psa_cipher_encrypt、psa_aead_encrypt、psa_asymmetric_encrypt
PSA_KEY_USAGE_DECRYPT0x0200上の復号側
PSA_KEY_USAGE_SIGN_MESSAGE0x0400psa_sign_message、psa_mac_compute
PSA_KEY_USAGE_VERIFY_MESSAGE0x0800psa_verify_message、psa_mac_verify
PSA_KEY_USAGE_SIGN_HASH0x1000psa_sign_hash(SIGN_MESSAGE も暗黙に含む)
PSA_KEY_USAGE_VERIFY_HASH0x2000psa_verify_hash(VERIFY_MESSAGE も含む)
PSA_KEY_USAGE_DERIVE0x4000鍵導出・鍵合意の入力にする
PSA_KEY_USAGE_VERIFY_DERIVATION0x8000導出結果の検証(psa_key_derivation_verify_*)
PSA_KEY_USAGE_EXPORT0x0001psa_export_key(値を取り出す)を許す
PSA_KEY_USAGE_COPY0x0002psa_copy_key を許す
PSA_KEY_USAGE_CACHE0x0004実装が鍵をメモリに保持し続けることを許す(性能のため)

用途を最小にするのが原則である。 署名鍵に EXPORT を付けてはいけないし、暗号化しかしない鍵に DECRYPT は要らない。 特に PSA_KEY_USAGE_EXPORT は「鍵をアプリケーションに返してよい」という宣言であり、 これを付けない限り、psa_export_key() は PSA_ERROR_NOT_PERMITTED で拒否される。 PSA の「鍵が外に出ない」保証は、このフラグを付けないことで成立している。

なお公開鍵はいつでもエクスポートできる。 psa_export_public_key() は用途フラグに関係なく成功する(公開鍵は秘密ではないので当然である)。

MAC の用途は SIGN / VERIFY.

MAC(メッセージ認証コード)の計算は PSA_KEY_USAGE_SIGN_MESSAGE、検証は PSA_KEY_USAGE_VERIFY_MESSAGE である。 「MAC は対称鍵だから ENCRYPT では?」と思って ENCRYPT を付けると NOT_PERMITTED になる。 PSA は「MAC=対称鍵による署名」と整理している。

psa_key_usage_t — 1 ビット 1 用途。OR で組み合わせるEXPORT 0x1値を取り出すCOPY 0x2複製CACHE 0x4メモリ保持ENCRYPT 0x100暗号化DECRYPT 0x200復号SIGN_MSG 0x400署名・MAC 計算VERIFY_MSG 0x800検証DERIVE 0x4000導出・鍵合意SIGN_HASH 0x1000 / VERIFY_HASH 0x2000 は SIGN_MSG / VERIFY_MSG を暗黙に含む(逆は含まない)署名鍵の最小SIGN_HASH のみ。EXPORT なしAEAD 鍵の最小ENCRYPT | DECRYPT。片方向なら片方だけMAC 鍵SIGN_MESSAGE | VERIFY_MESSAGE(ENCRYPT ではない)EXPORT を付けない限り psa_export_key() は NOT_PERMITTEDこれが「鍵が外に出ない」保証の実体。公開鍵は用途に関係なく psa_export_public_key() で取り出せる
用途は最小にする。特に EXPORT は「理由を説明できる鍵」だけに付ける

6. アルゴリズム psa_algorithm_t — どの計算に使ってよいか

属性の algorithm は、この鍵で使ってよいアルゴリズムを 1 つに固定する。 AES 鍵に PSA_ALG_GCM を設定すれば、その鍵で PSA_ALG_CTR を指定して暗号化しようとすると NOT_PERMITTED になる。

「なぜ 1 つに縛るのか」——同じ鍵を異なるアルゴリズムで使うと、片方の弱点でもう片方が壊れることがあるからだ。 例えば、同じ RSA 鍵を「署名」と「復号」の両方に使うと、攻撃者は署名させることで復号結果を手に入れられる(暗号編)。 鍵ごとにアルゴリズムを 1 つに固定するのは、この種の「用途混同」攻撃を構造的に防ぐためである。

psa_set_key_algorithm(&attr, PSA_ALG_ECDSA(PSA_ALG_SHA_256));  /* この鍵は ECDSA+SHA-256 専用 */

例外的に幅を持たせる方法もある。

PSA_ALG_NONE(値 0)のままだと、その鍵はどの暗号操作にも使えない(エクスポートや生データの保持だけになる)。

7. 寿命 psa_key_lifetime_t — 電源を切っても残るか、どこに置くか

寿命は 2 つの情報を 1 つの 32 ビット値に詰めたものである。

最もよく使う 2 つは定数になっている。

定数値意味
PSA_KEY_LIFETIME_VOLATILE0揮発。プログラム終了や psa_destroy_key() で消える。省略時の値
PSA_KEY_LIFETIME_PERSISTENT1永続。不揮発ストレージ(ITS。13 章)に暗号化して保存され、次回起動でも同じ ID で使える

永続鍵は 12 章で詳しく扱う。 この章では「揮発鍵は ID が自動で振られ、永続鍵は自分で ID を決める」という違いだけ覚えておけばよい。

psa_key_lifetime_t(32 ビット)= 場所 + 持続性location(場所)24 ビット0 = 実装の内部、1 = 主セキュアエレメント、0x800000〜 = ベンダ定義persistence 8 ビット0 = 揮発、1 = 永続、0xff = 読み取り専用VOLATILE = 0電源断・destroy で消える。ID は自動。既定PERSISTENT = 1ITS に保存。次回起動でも同じ ID。ID は自分で決める(1 << 8) | 1 = 0x101セキュアエレメント内の永続鍵。コードは変わらないPSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION(p, l) で組み立てる(12 章)
上位 24 ビットが「どこに置くか」、下位 8 ビットが「電源を切っても残るか」

8. ID psa_key_id_t — 永続鍵の名前

永続鍵を作るときは、psa_set_key_id() で 1 〜 0x3FFFFFFF の範囲の整数(PSA_KEY_ID_USER_MIN 〜 PSA_KEY_ID_USER_MAX)を自分で決めて設定する。 0x40000000 以上はベンダ・実装が予約している(TF-M の組み込み鍵、Mbed TLS が揮発鍵に割り当てる範囲など)。

#define KEY_ID_DEVICE_SIGN   ((psa_key_id_t)0x00000101)   /* プロジェクトで一元管理する */
psa_set_key_id(&attr, KEY_ID_DEVICE_SIGN);
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_PERSISTENT);

揮発鍵に ID を設定すると PSA_ERROR_INVALID_ARGUMENT になる(揮発鍵の ID は実装が振る)。 逆に永続鍵で ID を設定し忘れると、同じく INVALID_ARGUMENT である。

9. 属性の組み合わせ早見表

よく使う目的ごとに、属性の「正解セット」を並べる。 これをコピーして使えば、NOT_PERMITTED と INVALID_ARGUMENT の大半は避けられる。

目的typebitsusagealgorithm
AES-GCM でデータを暗号化・復号AES128/256ENCRYPT ∣ DECRYPTPSA_ALG_GCM
HMAC-SHA256 で認証タグHMAC256SIGN_MESSAGE ∣ VERIFY_MESSAGEPSA_ALG_HMAC(PSA_ALG_SHA_256)
デバイス署名鍵(ECDSA P-256)ECC_KEY_PAIR(SECP_R1)256SIGN_HASHPSA_ALG_ECDSA(PSA_ALG_SHA_256)
サーバの公開鍵で署名検証ECC_PUBLIC_KEY(SECP_R1)256VERIFY_HASHPSA_ALG_ECDSA(PSA_ALG_SHA_256)
ECDH で共有秘密を作るECC_KEY_PAIR(SECP_R1)256DERIVEPSA_ALG_ECDH または PSA_ALG_KEY_AGREEMENT(ECDH, HKDF(SHA_256))
X25519 鍵合意ECC_KEY_PAIR(MONTGOMERY)255DERIVEPSA_ALG_ECDH
HKDF のマスター秘密DERIVE任意DERIVEPSA_ALG_HKDF(PSA_ALG_SHA_256)
Ed25519 署名ECC_KEY_PAIR(TWISTED_EDWARDS)255SIGN_MESSAGEPSA_ALG_PURE_EDDSA
RSA-OAEP で鍵を包んで送るRSA_PUBLIC_KEY2048ENCRYPTPSA_ALG_RSA_OAEP(PSA_ALG_SHA_256)
相手に渡すための一時鍵(値を取り出す)AES128EXPORT (+ 必要な用途)任意

属性を組み立てる

10. 属性を読み出す

既存の鍵の属性は psa_get_key_attributes() で取り出せる。 ライブラリ関数が「渡された鍵が何者か」を確かめるときに使う。

psa_key_attributes_t a = PSA_KEY_ATTRIBUTES_INIT;
PSA_CHECK(psa_get_key_attributes(key, &a));
psa_key_type_t   t  = psa_get_key_type(&a);
size_t           b  = psa_get_key_bits(&a);
psa_key_usage_t  u  = psa_get_key_usage_flags(&a);
psa_algorithm_t  al = psa_get_key_algorithm(&a);
psa_reset_key_attributes(&a);       /* 必ず片付ける */

if (!PSA_KEY_TYPE_IS_ECC_KEY_PAIR(t) || !(u & PSA_KEY_USAGE_SIGN_HASH))
    return PSA_ERROR_INVALID_ARGUMENT;

この章のポイント