ai-agents-for-beginners

レッスンビデオを見る:暗号化されたレシートでAIエージェントを保護する

(マイクロソフトのコンテンツチームがマージ後にレッスン14/15のパターンに合わせてレッスン動画とサムネイルを追加予定です)

暗号化されたレシートでAIエージェントを保護する

はじめに

このレッスンで扱う内容:

学習目標

このレッスンを終えると、以下を理解できるようになります:

問題:あなたのエージェントの監査証跡

Contoso TravelのAIエージェントを展開したと想像してください。エージェントは顧客のリクエストを読み、フライトAPIを呼び出してオプションを検索し、顧客に代わって座席を予約します。前四半期にエージェントは50,000件の予約を処理しました。

今日、監査人が来ました。彼らは単純な質問をします:「あなたのエージェントが何をしたのか見せてください。」

あなたはログファイルを渡します。監査人はそれを見て、より難しい質問をします:「これらのログが改ざんされていないことをどうやって知れますか?」

これが監査証跡問題です。今日のほとんどのエージェント展開では以下に依存しています:

これらのどれも、監査人が誰かを信頼することなしに監査人の質問に答えることはできません(あなた、クラウドプロバイダー、データベースベンダー)。内部利用ではその信頼は時に許容されますが、規制されたワークロード(金融、医療、EU AI法の対象など)では許されません。

暗号化レシートは、各エージェントの行動を独立して検証可能にすることでこれを解決します。監査人はあなたを信頼する必要はありません。あなたの公開鍵とレシート自体だけがあれば十分です。

暗号化されたレシートとは?

レシートは、エージェントが行ったことを記録し、デジタル署名されたJSONオブジェクトです。

flowchart LR
    A[エージェントがツールを呼び出す] --> B[レシートペイロードを構築]
    B --> C[JSONを正規化 RFC 8785]
    C --> D[SHA-256 ハッシュ]
    D --> E[Ed25519 署名]
    E --> F[署名付きレシート]
    F --> G[監査人がオフラインで検証]
    G --> H{署名は有効か?}
    H -- yes --> I[改ざん検知証明]
    H -- no --> J[レシート拒否された]

最小限のレシートは以下のようになります:

{
  "type": "agent.tool_call.v1",
  "agent_id": "contoso-travel-bot",
  "tool_name": "lookup_flights",
  "tool_args_hash": "sha256:a3f9c1...",
  "result_hash": "sha256:7b2e1d...",
  "policy_id": "contoso-travel-policy-v3",
  "timestamp": "2026-04-25T14:30:00Z",
  "sequence": 47,
  "previous_receipt_hash": "sha256:9d4e6a...",
  "signature": {
    "alg": "EdDSA",
    "sig": "c5af83...",
    "public_key": "8f3b2c..."
  }
}

3つの性質が機能しています:

  1. 署名。レシートはエージェントのゲートウェイによってEd25519の秘密鍵で署名されます。対応する公開鍵を持つ者なら誰でも署名をオフラインで検証可能です。どのフィールドも改ざんされると署名は無効になります。

  2. 正準エンコード。署名前にレシートはJSON Canonicalization Scheme (JCS, RFC 8785) を使ってシリアル化されます。これにより、同一の意味内容を持つ2つの実装がバイト単位で同じ出力を生成します。正準化がなければ、異なるJSONシリアライザが同じ内容に異なる署名を生じさせます。

  3. ハッシュ連鎖previous_receipt_hash フィールドが各レシートを前のレシートにリンクします。レシートを削除または並び替えると、その後に続くすべてのレシートが壊れます。個々の署名が回避されてもチェーンレベルで改ざんが可視化されます。

これらの性質は3つの保証を提供します:

Pythonでレシートを作成する

レシート作成には特別なライブラリは不要です。暗号原始関数は広く利用可能で、ロジックは数十行のPythonコードです。

code_samples/18-signed-receipts.ipynb のハンズオン演習でフルフローを詳しく解説しています。要約版:

import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize  # RFC 8785 標準の JSON

def b64url_nopad(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")

def sha256_canonical(obj) -> str:
    """SHA-256 of a Python object's JCS-canonical JSON form."""
    return f"sha256:{hashlib.sha256(canonicalize(obj)).hexdigest()}"

# 署名キーを生成またはロードする(本番環境ではキー保管庫に保存)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key

# レシートのペイロードを作成(まだ署名なし)
tool_args = {"origin": "SYD", "destination": "LAX"}
tool_result = [{"flight": "QF11", "price": 1850, "stops": 0}]

payload = {
    "type": "agent.tool_call.v1",
    "agent_id": "contoso-travel-bot",
    "tool_name": "lookup_flights",
    "tool_args_hash": sha256_canonical(tool_args),
    "result_hash": sha256_canonical(tool_result),
    "policy_id": "contoso-travel-policy-v3",
    "timestamp": "2026-04-25T14:30:00Z",
    "sequence": 0,
    "previous_receipt_hash": None,
}

# 標準化し、ハッシュ化し、署名する。
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature

# 構造化された署名オブジェクトを添付する。
receipt = {
    **payload,
    "signature": {
        "alg": "EdDSA",
        "sig": b64url_nopad(signature_bytes),
        "public_key": b64url_nopad(bytes(verify_key)),
    },
}

これが署名パイプライン全体です。ノートブックの演習は各ステップを順に説明します。

レシートを検証し改ざんを検出する

検証は逆演算です:

import base64
import hashlib
from nacl import signing
from nacl.exceptions import BadSignatureError
from jcs import canonicalize

def b64url_decode(s: str) -> bytes:
    padding = "=" * ((4 - len(s) % 4) % 4)
    return base64.urlsafe_b64decode(s + padding)

def verify_receipt(receipt: dict) -> bool:
    # 署名は構造化されたオブジェクトです: {"alg", "sig", "public_key"}。
    sig_obj = receipt.get("signature")
    if not sig_obj or sig_obj.get("alg") != "EdDSA":
        return False

    # 実際に署名されたペイロード(署名を除くすべて)を再構築します。
    payload = {k: v for k, v in receipt.items() if k != "signature"}

    canonical_bytes = canonicalize(payload)
    message_hash = hashlib.sha256(canonical_bytes).digest()

    try:
        verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
        verify_key.verify(message_hash, b64url_decode(sig_obj["sig"]))
        return True
    except BadSignatureError:
        return False

この関数はレシートを受け取り、署名が有効なら True、そうでなければ False を返します。ネットワーク呼び出し不要、サービス依存なし、第三者の信頼も不要です。

改ざん検知の実例についてはノートブックで:

  1. 有効なレシートを作成し検証が成功すること確認。
  2. tool_args_hash フィールドの1バイトを変更。
  3. 検証を再実行し失敗を確認。

これがレシートが改ざん検知可能である実用的な証明です。どんなに小さな変更でも署名を壊します。

複数ステップのエージェント向けレシートの連鎖

1つの署名済みレシートは1つのアクションを保護します。レシートのチェーンがシーケンスを保護します。

flowchart LR
    R0[受領書 0<br/>ジェネシス] --> R1[受領書 1]
    R1 --> R2[受領書 2]
    R2 --> R3[受領書 3]
    R1 -. previous_receipt_hash .-> R0
    R2 -. previous_receipt_hash .-> R1
    R3 -. previous_receipt_hash .-> R2

それぞれのレシートは前のレシートのハッシュを記録します。レシート2を密かに削除するには攻撃者は:

秘密鍵がハードウェアキーボルト内にあり、公開鍵を各レシートに公開していれば、どちらの攻撃も検出なしには不可能です。

ノートブックでは以下を説明します:

  1. 3つのレシートのチェーンを構築。
  2. 各レシートの previous_receipt_hash が前のレシートの実際のハッシュと合致することを検証。
  3. 中間のレシートを改ざんし、その箇所でチェーンが壊れることを確認。

こうして外部監査人があなたを信頼しなくても検証可能な監査証跡を作ります。

レシートが証明すること(証明しないこと)

このセクションはレッスンの中で最も重要です。レシートは強力ですが、その力には限界があります。

レシートが証明する3つのこと:

  1. 帰属:特定の鍵が特定のペイロードに署名した。
  2. 完全性:署名後ペイロードが変更されていない。
  3. 順序:このレシートはそのレシートの後にチェーン内にある。

レシートが証明しないこと:

  1. 正確性:エージェントの行動が正しい行動だったこと。誤った回答に対してもきれいに署名可能です。
  2. ポリシー遵守policy_id のポリシーが実際に評価されたか、その行動を許可したか。レシートは主張を記録し、強制は記録しません。
  3. 鍵以外の身元:レシートは「この鍵がこの内容に署名した」と言いますが、「この人間が承認した」とは言いません。鍵から人または組織を結びつけるには別途身元インフラ(ディレクトリ、公開鍵レジストリなど)が必要です。
  4. 入力の真偽:エージェントが操作されたプロンプトを受けて行動しても、レシートはその行動を正確に記録します。レシートは入力検証の下流にあり、代わりにはなりません。

この境界が重要な理由は2つあります:

よくある誤解は、「レシートがあれば統治されている」ということですが、そうではありません。レシートは基盤です。統治はその上に構築するシステムです。

人間が正確な行動を承認したことを証明する

上記項目3は独立したセクションに値します:行動レシートは「この鍵がこの内容に署名した」と言い、「人間が承認した」とは言いません。高リスク行動(返金、削除、銀行送金など)には、統治フレームワークは欠けているその宣言をますます要求しています。これはこのレッスンで既に構築したプリミティブで生成可能です。

後続のノートブック code_samples/human-authorization-receipts.ipynb は、レッスンのレシートと同じエンベロープ形状(Ed25519でその正準SHA-256に署名された型付けペイロード、署名オブジェクトは署名バイトの外)で第二のレシート種類 human.approval.v1 を追加します。命名された承認者が実行前に完全な正準行動とそのダイジェストに署名し、エージェントの行動レシートは同じ行動ダイジェストparent_approval_ref(承認の receipt_hash)を持ちます。これは上で作った previous_receipt_hash と同じ慣習です。一つの verify_chain異なる固定鍵レジストリ(承認者鍵とエージェント鍵)で両アーティファクトを検証し、コードパスは共通ですが権限は別です。

ここで得られる性質は慎重に述べると:人間はこの正確な行動を承認し、エージェントはまさにその承認された行動を実行したということです。ノートブックの拒否フィクスチャはこの性質を主張ではなく実態にしています:

各失敗は明確な理由で拒否されるため、監査人は権限が陳腐化したのか実行行動が変わったのか判断できます。ノートブックが教えるルール:「署名された承認は単独で権限ではない。両方のレシートが実行時に同じ正準行動に結びつく場合にのみ権限は存在する」。このレッスンが従う同じInternet-Draft (draft-farley-acta-signed-receipts) にある共同署名パスがこのパターンの標準トラック形状です。

本番用の参考資料

このレッスンのPythonコードはあえて最小限にしているので全行読んで正確に何が起きているか理解できます。本番環境では2つの選択肢があります:

  1. 暗号プリミティブの直接利用。上記の50行は多くのユースケースに十分です。PyNaCl (Ed25519) と jcs パッケージ(正準JSON)は信頼性が高く監査済みのライブラリです。

  2. 本番用レシートライブラリの利用。いくつかのオープンソースプロジェクトが同じパターンを追加機能付きで実装しています(鍵ローテーション、一括検証、JWK Set配布、ポリシーエンジンと連携など):

    • 本レッスンで使うレシート形式はIETF Internet-Draft(draft-farley-acta-signed-receipts、リビジョン02)に従っており、共同適合性スイート(agent-governance-testvectors)を使って独立実装がバイト単位同一の正準出力を検証しています。
    • Microsoft Agent Governance ToolkitはCedarベースのポリシー決定とレシートを組み合わせています;リポジトリのチュートリアル33でエンドツーエンド例を参照。
    • protect-mcp (npm) と @veritasacta/verify (npm) パッケージはNodeベースのレシート署名・オフライン検証を提供し、署名保留フロー含む改ざん検出監査証跡をあらゆるMCPサーバにラップすることを想定(デスクトップフローのWebAuthn対応承認レシートパターンと同じヒューマン承認パターン)。
    • nobulex Python SDK (pip install nobulex) は同じEd25519 + JCS署名パターンをLangChainおよびCrewAI統合で提供。公開の相互検証テストベクトルとOWASP PR #2210 による準拠マッピングを提供。

自前実装とライブラリ利用の選択は、自前JWTライブラリを書くか信頼されたものを使うかの選択に似ています:どちらも合理的で、ライブラリは時間を節約し監査表面を減らし、自作は各プリミティブの理解を深めます。このレッスンはその両方の基盤を作るため自作ルートを教えます。

知識チェック

演習に進む前に理解度を確認しましょう。

1. レシートはエージェントの秘密Ed25519鍵で署名されています。監査人は公開鍵だけを持ちます。監査人はオフラインでレシートを検証できますか?

答え はい。Ed25519検証は署名バイトと公開鍵だけがあれば十分で、ネットワーク呼び出しもサービス依存もありません。これはレシートをエアギャップや複数組織、低信頼の監査環境で有用にする性質です。

2. 攻撃者がレシートの policy_id フィールドを、より寛容なポリシーのものに書き換えました。署名は元のペイロードに対して付与されています。検証時に何が起きますか?

答え 検証は失敗します。署名は元のペイロードの正規化されたバイトに対して計算されており、どのフィールドを変更しても正規化されたバイトが変わり、それによりSHA-256ハッシュが変わって署名が無効になります。攻撃者は新しい有効な署名を作成するために秘密鍵が必要ですが、持っていません。

3. なぜレシートには生の引数と結果ではなく tool_args_hashresult_hash が含まれているのですか?

回答 2つの理由があります。まず、レシートは生の内容(PIIやビジネスデータ)が漏れることが問題となる環境でアーカイブまたは伝送される可能性があります。ハッシュ化によりレシートは小さくなり内容は非公開のまま保たれます。監査人は別に保管された実際の内容のコピーとハッシュが一致することを検証します。次に、ハッシュは固定サイズです。レシートにハッシュがあることで、入力や出力がどんなに大きくてもサイズが一定に制限されます。

4. previous_receipt_hash フィールドは各レシートをその前のレシートと連結します。攻撃者がチェーンの途中のレシートをこっそり削除した場合、何が無効になりますか?

回答 削除されたレシート以降の全てのレシートです。これらの `previous_receipt_hash` フィールドは実際のチェーンと一致しなくなります(参照していたレシートが存在しないか、現在チェーンが異なる前任者を指しているため)。削除を隠すには、攻撃者は後続のすべてのレシートに再署名する必要があり、それには秘密鍵が必要です。

5. レシートが正常に検証された場合、それはエージェントの行動が正しい、健全、またはポリシーに適合していることを証明しますか?

回答 いいえ。有効なレシートは3つのことを証明します:帰属(この鍵がこの内容に署名した)、完全性(内容が変更されていない)、順序(このレシートはあのレシートの後にある)。しかし、それが行動が正しいこと、`policy_id` で示されたポリシーが実際に評価されたこと、エージェントがすべてのルールに従ったことを証明するわけではありません。レシートはエージェントの振る舞いを監査可能にするものであり、必ずしも正当性を保証するものではありません。これはこのレッスンで最も重要な境界線です。

練習問題

code_samples/18-signed-receipts.ipynb を開き、以下の4つのセクションを完了してください:

  1. セクション1:最初のレシートに署名して検証する。
  2. セクション2:レシートを改ざんして検証が失敗する様子を観察する。
  3. セクション3:3つのレシートのチェーンを作成し、チェーンの整合性を検証する。
  4. セクション4:Microsoft Agent Frameworkで構築したエージェントにパターンを適用する:ツール呼び出しをレシート署名でラップし、その後レシートを独立して検証する。

ストレッチチャレンジ1: 任意のフィールド(例:トレース用のリクエストID)を追加してレシートスキーマを拡張し、正規署名ロジックを更新し、レシートが検証に問題なく通ることを確認してください。その後、署名後にフィールドを変更して、検証が失敗することを確認してください。これにより、正規のエンコードの各バイトが署名にどのように寄与するか理解が深まります。

ストレッチチャレンジ2: 2つのレシートの正規化バイトを決定論的な順序で連結してSHA-256ハッシュを取り、そのダイジェストを3つ目のレシートの新しいフィールドとして埋め込み、署名してください。3つのレシートすべてが検証に問題なく通ることを確認します。これにより、一歩入り込んだ包含証明が構築されます:3つ目のレシートを持つ者は、最初の2つがその署名時点で存在していたことを内容を開示せずに証明できます。これはセレクティブ・ディスクロージャレシートが大規模に使用するパターンです(Merkleコミットメント、RFC 6962)。

結論

暗号化レシートはAIエージェントに以下の監査証跡を提供します:

それらは入力検証やポリシー適用、身元基盤の代替ではありません。これらの層の基盤となるものです。規制されたワークロード、多機関のワークフロー、将来の監査人があなたを信用しない可能性がある環境にエージェントを展開する場合、監査証跡を誠実に保つ手段としてレシートが必要です。

最も重要な要点:レシートは「誰が何を言ったか」を証明しますが、「言われたことが真実または正しいこと」を証明しません。この区別を強く意識してください。これは誠実な出自システムと誤解を招くものの違いです。

本番環境向けチェックリスト

このレッスンから卒業し、レシート署名済みエージェントを実運用に展開する準備ができたら:

AIエージェントのセキュリティについてもっと質問がありますか?

Microsoft Foundry Discord に参加して、他の学習者と会い、オフィスアワーに参加し、AIエージェントに関する質問に答えを得てください。

このレッスンの先へ

このレッスンは単一レシート署名とハッシュチェーンのシーケンスをカバーしています。同じプリミティブを組み合わせて、ガバナンス体制が成熟すると遭遇するかもしれないいくつかの高度なパターンが作られます:

追加リソース

前回のレッスン

ローカルAIエージェントの作成


免責事項: 本書類は AI 翻訳サービス Co-op Translator を使用して翻訳されています。正確性を期していますが、自動翻訳には誤りや不正確な部分が含まれる可能性があることをご承知おきください。原文の原語版が正式な情報源とみなされるべきです。重要な情報については、専門の人間による翻訳を推奨します。本翻訳の利用により生じたいかなる誤解や解釈違いについても、当方は責任を負いかねます。