単回使用の値

ときには、_一度しか使えない_値が欲しいことがあります。その重要な例の 1 つが、暗号分野における「Nonce」です。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct Key(/* 詳細は省略 */);

/// 暗号用途に適した単回使用の数値。
pub struct Nonce(u32);

/// 暗号学的に安全な乱数生成関数。
pub fn new_nonce() -> Nonce {
    Nonce(4) // 公平なサイコロを振って選ばれました, https://xkcd.com/221/
}

/// nonce を消費しますが、key や data は消費しません。
pub fn encrypt(nonce: Nonce, key: &Key, data: &[u8]) {}

fn main() {
    let nonce = new_nonce();
    let data_1: [u8; 4] = [1, 2, 3, 4];
    let data_2: [u8; 4] = [4, 3, 2, 1];
    let key = Key(/* 詳細は省略 */);

    // key と data は再利用したり、コピーしたりできますが、nonce はできません。
    encrypt(nonce, &key, &data_1);
    // encrypt(nonce, &key, &data_2); // 🛠️❌
}
  • 問題: 値が一度しか使われないことを、どうすれば保証できるでしょうか?

  • 動機: nonce は、リプレイ攻撃を防ぐために暗号プロトコルで使用される、ランダムで一意なデータです。

    背景: 実際には、誤って nonce を再利用してしまった事例があります。 最も一般的には、その結果として暗号プロトコルが完全に破綻し、 本来の役割を果たせなくなります。

    nonce の再利用のされ方や対象の暗号方式によっては、秘密鍵まで 攻撃者に計算されてしまうこともあります。

  • Rust には、「これを一度使ったら、もう二度と使えない」という不変条件を 実現するための分かりやすい手段があります。値を 所有権を受け取る引数 として渡すことです。

  • 注目点: encrypt 関数は nonce を値渡し(所有権を受け取る引数)で 受け取りますが、keydata は参照で受け取ります。

  • 単回使用の値に対するテクニックは次のとおりです。

    • コンストラクターを非公開にして、同じ内部値を持つ値をユーザーが 2 回 構築できないようにする。

    • 一意に保ちたいデータをユーザーが複製できないように、Clone/Copy トレイトや同等のメソッドを実装しない。

    • 内部の型を不透明にし(newtype パターンのように)、ユーザーが既存の値を 自分で変更できないようにする。

  • 問い: スライドのコードにある newtype パターンには、何が欠けているでしょうか?

    期待する答え: モジュール境界。

    実演: モジュール境界がないと、ユーザーは nonce を自分で構築できてしまいます。

    修正: KeyNoncenew_nonce をモジュールの内側に置きます。

さらに探る

  • 暗号における注意点: nonce は、実際のランダム性がない疑似乱数過程で 生成された場合、依然として 2 回使われる可能性があります。これはこの方法では 防げません。この API 設計は 1 つの nonce を複製することは防ぎますが、 すべてのロジックバグを防ぐわけではありません。