(課程影片與縮圖將由 Microsoft 內容團隊於合併後新增,符合第 14 / 15 課的模式。)
本課程將涵蓋:
完成本課後,您將能夠:
想像您已部署 Contoso Travel 的 AI 代理。代理讀取客戶請求,呼叫航班 API 查詢選項,並代表客戶訂位。上季,代理處理了 5 萬筆訂位。
今天稽核員來了。他們問一個簡單問題:「請展示代理的行為。」
您交出日誌檔案。稽核員看過後問更困難的問題:「我怎麼知道這些日誌沒有被修改?」
這就是審計軌跡問題。現今大多數代理部署依賴於:
這些方法都無法直接回答稽核員的問題,除非他們信任某方(您、雲端提供者、資料庫廠商)。公司內部使用時這種信任尚可接受,但對受規範管制的工作負載(金融、醫療、受歐盟 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..."
}
}
三個屬性發揮作用:
簽章。收據由代理入口使用 Ed25519 私鑰簽名。任何擁有對應公鑰的人皆可離線驗證簽章。任一欄位被竄改會使簽章失效。
規範編碼。簽名前,收據使用 JSON 規範化方案(JCS,RFC 8785)序列化。這確保兩個實作產生同樣邏輯收據時,輸出位元組皆相同。若無此規範化,不同 JSON 序列化器會針對相同內容生成不同簽章。
哈希串接。previous_receipt_hash 欄位將每張收據串接至前一張。移除或重排收據會破壞後續每張收據。即使個別簽章被繞過,竄改在鏈結層面仍可見。
這些特性提供三個保證:
您不需要特別函式庫就能產生收據。密碼學基礎元件普遍可用,邏輯只有幾十行 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。無需網路呼叫、服務依賴,也不需信任第三方。
欲目睹竄改偵測實作,筆記本示範:
tool_args_hash 欄位的一個位元組。這是收據防篡改的實際演示:任何細微改動都會破壞簽章。
單一簽名收據保護單一步驟。收據鏈則保護整個序列。
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 張收據,需要:
previous_receipt_hash 欄位(會破壞第 3 張收據的簽章),或若私鑰存於硬體金鑰庫,且你將公鑰隨每張收據一併發布,將無法未被察覺地成功發動上述攻擊。
筆記本涵蓋:
previous_receipt_hash 是否與前一張收據的實際雜湊匹配。這就是如何產生稽核員可獨立驗證、不需信任您的審計軌跡。
此區為本課最重要內容。收據功能強大,但其能力有限。
收據能證明三件事:
收據不能證明:
policy_id 所指政策是否真正被評估,或是否允許此行動。收據記錄了聲稱的行為,不代表有執行政策。此界線重要因為:
常見錯誤是誤認「有收據」即「受到管控」。事實非也。收據是基礎,治理是您建立其上的系統。
本課 Python 程式碼刻意保持簡潔,使您能逐行理解運作原理。生產環境有兩種選擇:
直接基於密碼學基元建構。 如上所示約 50 行足夠應用。PyNaCl(Ed25519)和 jcs 套件(規範 JSON)皆為維護良好且經審計的函式庫。
使用生產級收據函式庫。 有多個開源專案實作相同模式並有額外功能(鑰匙輪替、批次驗證、JWK 集合分發、與政策引擎整合):
draft-farley-acta-signed-receipts),現正標準化流程中。protect-mcp (npm) 與 @veritasacta/verify (npm) 套件提供 Node.js 版本的收據簽署與離線驗證實作,適用於為任何 MCP 伺服器包裝防篡改稽核軌跡。pip install nobulex) 提供相同 Ed25519 + JCS 簽署模式,具 LangChain 與 CrewAI 整合,公布交叉驗證測試向量,並透過 OWASP PR #2210 貢獻合規映射。自行打造與使用函式庫的抉擇,如同自寫 JWT 函式庫與採用驗證過函式庫:兩者合理;函式庫節省時間、降低稽核面;自行打造則需理解每個基元。此課教您從零落筆,為兩種選擇打下基礎。
在進行練習前自測理解。
1. 收據以代理的 Ed25519 私鑰簽署。稽核員僅擁有公鑰,能否離線驗證收據?
2. 攻擊者篡改收據中的 policy_id 欄位,宣稱適用更寬鬆政策。簽章針對原始載荷計算,驗證時發生什麼事?
3. 為何收據包含 tool_args_hash 與 result_hash,非原始參數與結果?
4. previous_receipt_hash 欄位將每張收據與前一張鏈接。若攻擊者悄悄刪除鏈中間某張收據,何者失效?
5. 收據驗證通過,是否證明代理行動正確、合理且符合政策?
開啟 code_samples/18-signed-receipts.ipynb 並完成以下四部分:
彈性挑戰 2: 對您的兩份收據進行 SHA-256 雜湊串接(以確定性順序串接其標準位元),並將結果摘要作為第三份收據上的新欄位,在簽署前嵌入該欄位。驗證所有三份收據仍可往返。您剛剛建立了一步包含證明:任何持有第三份收據的人,都能證明前兩份收據在簽署時存在,且無需揭露其內容。這是選擇性揭露收據在大規模使用的模式(Merkle 承諾,RFC 6962)。
密碼學收據為 AI 代理提供了以下的審計軌跡:
它們並非輸入驗證、政策執行或身份基礎設施的替代品,而是這些層的基礎。當您將代理部署在受規管的工作負載、多組織流程或任何未必可假設未來審計者信任您的環境中,收據就是讓審計軌跡忠實的方式。
最重要的重點:收據證明了誰在何時說了什麼。它們不證明內容的真實性或正確性。請緊握此區別。這是誠實根源系統與誤導系統的差別。
當您準備從此課程晉級,部署帶有收據簽章的代理於實際環境:
https://your-org.example.com/.well-known/agent-keys.json。加入 Microsoft Foundry Discord 與其他學員交流、參加辦公時間,並獲得您的 AI 代理相關問題解答。
本課程涵蓋單一收據簽署和雜湊鏈序列。相同原語組合成幾種您可能會在治理成熟階段遇到的進階模式:
authorization_*)與執行後(result_*)兩半,分別獨立簽章,有用於授權決策與觀察結果由不同執行者或時間產生的情境。此模式可與本課教學的收據格式相加組合。result_hash 的任意位元。實務載荷往往比單一工具調用結果更豐富:決策前的推理(模型預測、考慮選項、證據與其完整性、風險狀態、責任鏈、通過結果)皆可存放於載荷中,由單一收據封存。這讓收據格式保持簡潔,並允許領域別載荷模式演化。signature.alg 欄位可承載 ML-DSA-65(NIST 後量子簽章標準),以需求遷移。規劃收據雙簽過渡期。(由課程維護者待定)
免責聲明: 此文件已使用 AI 翻譯服務 Co-op Translator 進行翻譯。雖然我們努力追求準確性,但請注意自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應視為權威來源。對於關鍵資訊,建議採用專業人工翻譯。我們不對因使用此翻譯所產生的任何誤解或誤譯承擔責任。