(课程视频和缩略图将在合并后由微软内容团队添加,符合第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
每张收据记录前一张收据的哈希。要悄无声息地移除第二张收据,攻击者必须:
previous_receipt_hash 字段(会破坏第 3 张收据的签名),或如果私钥存储在硬件密钥库中,且您发布的每张收据都携带公钥,以上攻击均无法在不被发现的情况下实现。
笔记本演示了:
previous_receipt_hash 是否匹配前一张收据的实际哈希。这就是如何构建外部审计员无需信任您即可验证的审计追踪。
这是本课最重要的部分。收据很强大,但其能力有限。
收据能证明三件事:
收据不能证明:
policy_id 中的策略是否实际被评估,或即使检查过,是否会允许该操作。收据记录的是声明内容,而非执行结果。这个边界很重要,原因有二:
一个常见错误是假设“有收据”即意味着“受治理”。事实并非如此。收据是基础,治理是你建立的体系。
上文第三点值得单独成章:一张操作收据说明“该密钥签署此内容”,但绝不表述“某人批准此操作”。对高风险操作(退款、删除、电汇),治理框架越来越要求提供这份缺失声明,且使用本课已有的原语即可实现。
后续笔记本 code_samples/human-authorization-receipts.ipynb 新增一种收据类型 human.approval.v1,其包封格式与本课收据相同(带类型的负载,通过 Ed25519 对规范化的 SHA-256 摘要签名,且 signature 对象位于签名字节之外)。被命名的审批人先签署完整规范操作及其摘要,执行前;代理的操作收据携带相同的操作摘要及 parent_approval_ref,即该批准收据的 receipt_hash,采用与链中 previous_receipt_hash 相同的约定。一个 verify_chain 函数将两个收据在分开的固定公钥注册表(审批人和代理人公钥)下验证,代码路径相同,权威方不同。
其属性经过谨慎表述:人类批准了这确切操作,代理执行了完全批准的操作。 笔记本中针对拒绝情形的测试用例使该属性真实(非口头):
每种失败都有独特理由,审计员看到拒绝便知是权威过期还是执行内容变动。笔记本教你的规则是:签名的批准本身不构成权威。权威仅在执行时两个收据仍捆绑到相同规范操作时成立。同一互联网草案(draft-farley-acta-signed-receipts)中的联签方案是该模式的标准轨形态。
本课 Python 代码刻意精简,让你读懂每行代码完整含义。生产环境可选两种方案:
直接基于加密原语搭建。 前面展示的 50 行代码应付许多场景足够。PyNaCl(Ed25519)和 jcs 包(规范 JSON)都是维护良好的审计库。
使用生产用收据库。 若干开源项目实现了相同模式并加添附加功能(密钥轮换、批量验证、JWK 集分发、与策略引擎整合):
draft-farley-acta-signed-receipts,修订02),且有共享合规套件(agent-governance-testvectors),独立实现可交叉验证字节一致的规范输出。protect-mcp (npm)和 @veritasacta/verify (npm)包提供了基于 Node 的收据签名及离线验证实现,可包装任何 MCP 服务器生成防篡改审计链,包括支持的共签流程——暂停操作发出绑定操作摘要的批准收据(桌面流程中基于 WebAuthn),与上述人类授权笔记本采用的批准收据方案相同。pip install nobulex)在 Python 中实现相同的 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 并完成以下四个部分:
拓展挑战 1: 在收据模式中添加一个你自己选择的字段(比如用于追踪的请求 ID),更新规范签名逻辑以包括它,并确认收据仍能通过验证。然后签名后修改该字段并确认验证失败。这迫使你理解规范编码的每个字节如何影响签名。
拓展挑战 2: 对两份收据的规范字节进行 SHA-256 哈希(以确定顺序连接字节),将所得摘要作为新字段嵌入第三份收据中后签名。验证三份收据仍能通过验证。你刚建立了一步包含证明:任何持有第三份收据的人均可证明第一和第二份在签署时存在,而无需公开其内容。这是选择性披露收据在大规模使用的模式(默克尔承诺,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 翻译完成。尽管我们力求准确,但请注意,自动翻译可能包含错误或不准确之处。原始语言版文件应视为权威来源。对于重要信息,建议使用专业人工翻译。我们对因使用本翻译而产生的任何误解或误释不承担责任。