(課程影片和縮圖將由微軟內容團隊在合併後新增,符合課程第14 / 15課的模式。)
本課程將涵蓋:
完成本課程後,你將能夠:
想像你為 Contoso Travel 部署了一個 AI 代理。該代理閱讀客戶請求,呼叫航班 API 查詢選項,並代表客戶預訂座位。上季度代理處理了 50,000 筆預訂。
今天有審計員到訪。他們提出一個簡單問題:「告訴我你的代理做了什麼。」
你交出日誌檔案。審計員看了之後提出更棘手的問題:「我怎麼知道這些日誌沒有被編輯過?」
這就是審計追蹤的問題。目前多數代理部署依賴:
這些方法都無法在不要求審計員信任某人(你、你的雲端供應商、你的資料庫廠商)的前提下回應問題。對內部使用,這種信任通常可接受,但對受規範工作負載(金融、醫療、任何須遵守歐盟 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 的收據簽署與離線驗證實作,適用於為 MCP 伺服器包裝可偵測竄改審計追蹤。自行實作與使用函式庫的抉擇如同自寫 JWT 函式庫與用現成一樣:兩者皆合理;函式庫節省時間並降低審計範圍;自行實作可理解每個基元。本課提供自行實作路徑,讓你兩種選擇都打下基礎。
開始練習前測試你的理解。
1. 收據由代理的 Ed25519 私鑰簽署。審計員只有公鑰。審計員可離線驗證收據嗎?
2. 攻擊者修改收據中 policy_id 欄位,宣稱受一個較寬鬆政策管控。簽章基於原始負載。驗證時會發生什麼?
3. 為何收據包含 tool_args_hash 和 result_hash,而非原始參數與結果?
4. previous_receipt_hash 欄將每筆收據連結到前一筆。若攻擊者悄悄刪除鏈中間的收據,會造成什麼失效?
5. 收據驗證通過。這是否證明代理行動正確、合理或遵守政策?
打開 code_samples/18-signed-receipts.ipynb,完成全部四個部分:
進階挑戰1: 擴充收據結構,新增自訂欄位(例如追蹤用請求 ID),更新標準簽署邏輯包含此欄,並確認收據可成功驗證。簽章後修改欄位並確認驗證失敗。此挑戰強迫你理解標準化編碼每個位元如何影響簽章。 挑戰進階 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 欄位可帶入 NIST 後量子簽章標準 ML-DSA-65,以利遷移。規劃過渡期間並行雙重簽署。(由課程維護者決定)
免責聲明: 本文件使用 AI 翻譯服務 Co-op Translator 進行翻譯。雖然我們力求準確,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應被視為權威來源。對於重要資訊,建議尋求專業人工翻譯。我們不對因使用本翻譯而引起的任何誤解或曲解承擔責任。