Chapter 05
乱数とハッシュ — 鍵を使わない 2 つの基本操作
この章のゴール.
psa_generate_random()とpsa_hash_*()を、 「一発関数」と「分割関数」の両方で使えるようになり、 なぜ分割関数の状態遷移がこの形なのかを説明できるようになること。この章で使う既出の用語(定義は各リンク先). PSA(01 章 1 節)、ビット(02 章 4 節)、操作オブジェクト(02 章 5 節)、algorithm(03 章 11 節)、generate(04 章 10 節)、import(04 章 10 節)、作成(04 章 1 節)、使用(04 章 1 節)
1. この 2 つだけは鍵が要らない
Crypto API のほとんどの操作は鍵 ID を受け取るが、乱数生成とハッシュだけは鍵を使わない。 鍵の属性の話をひとまず忘れて、API の作法(02 章)そのものを練習するのに最適である。
- 乱数(random): 予測できないバイト列を作る。鍵の生成、ノンス(一度きりの値)、チャレンジ(相手に署名させる使い捨ての値)に使う
- ハッシュ(hash): 任意長のデータから固定長(SHA-256 なら 32 バイト)の「指紋」を作る。同じ入力なら必ず同じ出力、1 ビット違えば全く別の出力、出力から入力は逆算できない
2. psa_generate_random() — 乱数
psa_status_t psa_generate_random(uint8_t *output, size_t output_size);引数はバッファとその長さだけで、output_size バイトが必ず埋まる(この関数だけは「実長」を返さない)。
uint8_t nonce[12];
PSA_CHECK(psa_generate_random(nonce, sizeof nonce));PSA の乱数は DRBG(Deterministic Random Bit Generator、決定論的乱数生成器)—— 物理的なエントロピー源(乱数の元になる予測不能なゆらぎの供給源)から得た「種」を元に、 暗号学的に安全な擬似乱数を大量に作る仕組み——の出力である。 Mbed TLS では CTR_DRBG(AES を使う DRBG)か HMAC_DRBG が使われる。
初心者が知っておくべきことは 3 つである。
rand()やHAL_GetTick()を鍵やノンスに使ってはいけない。予測できる乱数は、暗号を無力化する(過去に多数の実被害がある)psa_generate_random()がPSA_ERROR_INSUFFICIENT_ENTROPYを返すなら、エントロピー源が設定されていない。マイコンでは TRNG(True Random Number Generator、物理現象を使う真性乱数ハードウェア)を Mbed TLS に接続する作業が必要で、これが動かないとpsa_crypto_init()自体が失敗する(15 章)- 乱数を「鍵」として使いたいなら、
psa_generate_random()で作ってpsa_import_key()するのではなく、psa_generate_key()を使う。値がアプリケーションのメモリを通らないで済む
3. ハッシュ — 一発関数
psa_status_t psa_hash_compute(psa_algorithm_t alg,
const uint8_t *input, size_t input_length,
uint8_t *hash, size_t hash_size, size_t *hash_length);
psa_status_t psa_hash_compare(psa_algorithm_t alg,
const uint8_t *input, size_t input_length,
const uint8_t *hash, size_t hash_length);psa_hash_compute() はデータを渡すとハッシュを返す。 psa_hash_compare() は「このデータのハッシュは、この値と一致するか」を判定し、一致なら PSA_SUCCESS、不一致なら PSA_ERROR_INVALID_SIGNATURE を返す。
uint8_t digest[PSA_HASH_LENGTH(PSA_ALG_SHA_256)];
size_t digest_len;
PSA_CHECK(psa_hash_compute(PSA_ALG_SHA_256, data, data_len, digest, sizeof digest, &digest_len));
/* ダウンロードしたイメージの期待値と照合 */
st = psa_hash_compare(PSA_ALG_SHA_256, image, image_len, expected, 32);
if (st == PSA_ERROR_INVALID_SIGNATURE) { /* 壊れているか改ざん */ }なぜ
memcmpではなくpsa_hash_compareなのか.
memcmpは最初に違うバイトが見つかった瞬間に終わるので、一致した長さが処理時間に現れる。 攻撃者は応答時間を測ることで、1 バイトずつ正解に近づける(タイミング攻撃)。 ハッシュの照合ではこれは致命的ではないが、MAC の照合(06 章)では致命的であり、 PSA は「比較は必ずライブラリの中で、一定時間で」という作法を全体で統一している。 だからpsa_hash_compare、psa_mac_verify、psa_verify_hashという「検証専用関数」が用意されている。
使えるハッシュのアルゴリズム定数:
| 定数 | 出力(バイト) | 用途 |
|---|---|---|
PSA_ALG_SHA_256 | 32 | 標準。迷ったらこれ |
PSA_ALG_SHA_384 | 48 | P-384 と組む。TLS の一部 |
PSA_ALG_SHA_512 | 64 | Ed25519 の内部で使用 |
PSA_ALG_SHA_224 | 28 | 古い規格の互換 |
PSA_ALG_SHA3_256 | 32 | SHA-3 系(SHA-2 とは別設計の新しいハッシュ族)。対応実装は少ない |
PSA_ALG_SHA_1 | 20 | 新規設計では使わない(衝突が発見済み)。既存プロトコルの互換のみ |
PSA_ALG_MD5 | 16 | 同上。互換のみ |
出力長は PSA_HASH_LENGTH(alg) で得られる。アルゴリズムが実行時に決まるなら PSA_HASH_MAX_SIZE(実装が対応する最大。通常 64)でバッファを取る。
4. ハッシュ — 分割関数
psa_hash_operation_t op = PSA_HASH_OPERATION_INIT;
psa_status_t psa_hash_setup(psa_hash_operation_t *op, psa_algorithm_t alg);
psa_status_t psa_hash_update(psa_hash_operation_t *op, const uint8_t *input, size_t input_length);
psa_status_t psa_hash_finish(psa_hash_operation_t *op, uint8_t *hash, size_t hash_size, size_t *hash_length);
psa_status_t psa_hash_verify(psa_hash_operation_t *op, const uint8_t *hash, size_t hash_length);
psa_status_t psa_hash_abort(psa_hash_operation_t *op);
psa_status_t psa_hash_clone(const psa_hash_operation_t *src, psa_hash_operation_t *dst);ファームウェアイメージのように全体をメモリに置けないデータや、 通信のように少しずつ届くデータをハッシュするときは、分割関数を使う。
psa_status_t hash_flash_region(uint32_t addr, size_t total, uint8_t out[32])
{
psa_hash_operation_t op = PSA_HASH_OPERATION_INIT;
uint8_t buf[256];
size_t out_len, done = 0;
psa_status_t st;
st = psa_hash_setup(&op, PSA_ALG_SHA_256);
if (st != PSA_SUCCESS) goto cleanup;
while (done < total) {
size_t n = (total - done < sizeof buf) ? total - done : sizeof buf;
flash_read(addr + done, buf, n);
st = psa_hash_update(&op, buf, n); /* 何回でも。長さは任意 */
if (st != PSA_SUCCESS) goto cleanup;
done += n;
}
st = psa_hash_finish(&op, out, 32, &out_len); /* 成功すると op は inactive に戻る */
cleanup:
psa_hash_abort(&op); /* 失敗時の後始末。成功時は無害 */
return st;
}4.1 状態遷移
分割ハッシュの操作オブジェクトは、次の状態を持つ。
- inactive: 初期化直後、または
finish/verify/abortの後 - active:
setup成功後。updateを受け付ける - error: どれかの関数が失敗した後。
abort以外は受け付けない
update に渡すデータはどう分割しても結果は同じである。 「256 バイトずつ」でも「1 バイトずつ」でも「全部一度に」でも、finish の出力は一致する。 これは、ハッシュ関数が内部で 64 バイト(SHA-256 の場合)のブロック単位で処理しつつ、端数を操作オブジェクトの中に溜めているからである。
4.2 psa_hash_clone() — 途中まで計算した状態を分岐する
ハッシュだけにある特別な関数が psa_hash_clone() である。 active な操作オブジェクトを複製し、「ここまでの共通部分 + それぞれ違う末尾」を 2 回計算する手間を省く。
典型は、共通ヘッダの後に複数のバリアント(末尾だけ違う版)が続く場合や、 TLS のハンドシェイク(通信の冒頭で鍵を決める手順)で「ここまでの全メッセージのハッシュ」を何度も取る場合である。
psa_hash_operation_t base = PSA_HASH_OPERATION_INIT, branch = PSA_HASH_OPERATION_INIT;
psa_hash_setup(&base, PSA_ALG_SHA_256);
psa_hash_update(&base, header, header_len); /* 共通部分 */
psa_hash_clone(&base, &branch); /* branch は inactive でなければならない */
psa_hash_update(&branch, tail_a, tail_a_len);
psa_hash_finish(&branch, hash_a, 32, &len); /* ヘッダ+A のハッシュ */
psa_hash_update(&base, tail_b, tail_b_len);
psa_hash_finish(&base, hash_b, 32, &len); /* ヘッダ+B のハッシュ */memcpy で操作オブジェクトを複製してはいけない理由もここにある—— ハードウェアアクセラレータ(暗号計算を肩代わりする専用回路)を使う実装では、操作の状態がハードウェアの中にあり、構造体のコピーでは複製にならない。 psa_hash_clone() は、実装がそれを正しく行う唯一の方法である。
5. ハッシュが使われる場面と、PSA での担当
「ハッシュを取る」という操作は単独では意味を持たず、常に何かの部品である。 PSA の中でどの操作がハッシュを内包しているかを知っておくと、自分でハッシュを取る必要があるかどうかが判断できる。
| 場面 | 自分でハッシュを取る? | PSA での呼び方 |
|---|---|---|
| ファイル・イメージの完全性確認 | する | psa_hash_compute / psa_hash_compare |
| 署名の作成・検証 | どちらでも。psa_sign_message は内部で取る。psa_sign_hash は自分で取って渡す | 09 章 |
| MAC | しない(HMAC の内部でハッシュが回る) | 06 章 |
| 鍵導出 | しない(HKDF の内部で HMAC が回る) | 10 章 |
| パスワードの保存 | ハッシュ単体は不可。PBKDF2 を使う | 10 章 |
| TLS ハンドシェイクの転写ハッシュ | 分割ハッシュ + clone | ライブラリの内部 |
ハッシュは「秘密」を守らない.
ハッシュには鍵がないので、誰でも同じ値を計算できる。 「ハッシュを付けたから改ざんが分かる」は、攻撃者がデータとハッシュの両方を書き換えられるなら成り立たない。 改ざん検出に必要なのは鍵付きのハッシュ、すなわち次章の MAC か、09 章の署名である。 ハッシュ単体で意味があるのは、ハッシュ値そのものが信頼できる経路で届く場合(署名済みのマニフェスト——更新内容の一覧を記した目録——に書かれている、など)だけである。
6. 手を動かす
ハッシュの性質を確かめる
7. 仕様書に逃がす
| 関数・定数 | 用途 |
|---|---|
psa_hash_suspend() / psa_hash_resume() | 途中状態をバイト列にして保存・復元する(Crypto API 1.1 で追加。長時間の処理を中断するとき)。対応実装は少ない |
PSA_HASH_BLOCK_LENGTH(alg) | ハッシュ関数の内部ブロック長(HMAC の鍵長の目安を出すとき) |
PSA_ALG_SHAKE256_512、PSA_ALG_SHA3_* | SHA-3 系。ポスト量子暗号(量子コンピュータでも破れないよう設計された暗号)の実装で使われ始めている |
この章のポイント
- 乱数とハッシュは鍵を使わない唯一の操作。API の作法の練習に使う
psa_generate_random()は必ずoutput_sizeバイト埋める。rand()は絶対に使わない- 一発関数
psa_hash_compute/psa_hash_compare、分割関数setup → update… → finish - 比較は
memcmpではなく検証専用関数(一定時間で比較する) - ハッシュ単体は改ざんを防げない。鍵付き(MAC・署名)にして初めて意味を持つ