L22 安全的 Agent:用加密签名给每个动作"上保险"
课程目标:理解 AI Agent 的审计挑战,掌握 Ed25519 加密签名的"收据"机制,能用 Python 签发和验证 Agent 的工具调用收据,理解收据能证明什么、不能证明什么。
一、监管者来了,Agent 能证明自己没作弊吗?#
想象你的 Agent 一年处理 5 万笔退款。
突然有一天,监管者来了:
"请证明这 5 万笔退款没有被篡改、被伪造、被双重支付。"
你翻出日志一看:
2026-01-15 09:23:11 | refund(order_1234, $50) | success
2026-01-15 09:24:22 | refund(order_1235, $80) | success
...
这些日志是 Agent 自己写的——任何有文件系统权限的人都能改。
这就是 Securing AI Agents 想解决的核心问题:
怎么让 Agent 的每个动作都独立可验证——即使系统管理员改了日志、即使数据库被入侵,监管者也能验证动作的真实性?
答案:加密收据(Cryptographic Receipts)——用 Ed25519 给每个工具调用签名。
二、用一个比喻:Agent 收据 = 纸质发票的"防伪印章"#
把 Agent 工具调用比作购物小票:
| 购物小票 | Agent 收据 |
|---|---|
| 商家盖章 | Ed25519 数字签名 |
| 发票编号 | 序列号 + 时间戳 |
| 防伪码 | 哈希链(每张票链上一张) |
| 普通纸(小偷能写) | 签名收据(伪造不了) |
核心区别:
普通日志:Agent 自己写的小票——商家说"我卖给你",你信了?
加密收据:带防伪印章的小票——独立第三方能验证"这确实是店家卖的"
三、加密收据是什么?#
Securing AI Agents 给出的标准格式:
{
"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 签名 | 只有私钥持有者能签发 |
| JCS (RFC 8785) | 序列化的确定性(避免换行/空格伪造) |
| 哈希链 | 上一张收据的指针 |
四、用 Python 签发收据#
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize
def b64url_nopad(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
def sha256_canonical(obj) -> str:
return f"sha256:{hashlib.sha256(canonicalize(obj)).hexdigest()}"
# 生成密钥对
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Agent 的动作
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)
signature_bytes = signing_key.sign(canonical_bytes).signature
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
五、验证收据——离线、零依赖#
Securing AI Agents 给的验证函数:
from nacl.exceptions import BadSignatureError
def verify_receipt(receipt: dict) -> bool:
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# 1. 拿出签名的 payload(去掉 signature 字段)
payload = {k: v for k, v in receipt.items() if k != "signature"}
# 2. 用同样的 canonicalization 重新序列化
canonical_bytes = canonicalize(payload)
# 3. 用公钥验签
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(canonical_bytes, b64url_decode(sig_obj["sig"]))
return True
except BadSignatureError:
return False
关键特性:
离线验证:不需要网络、不需要服务依赖
只需要公钥:任何人都能验证
检测篡改:任何字段被修改 → 验证失败
六、防篡改演示#
# 1. 原始收据
original_receipt = issue_receipt(...)
assert verify_receipt(original_receipt) # True
# 2. 篡改 tool_args_hash
tampered = original_receipt.copy()
tampered["tool_args_hash"] = "sha256:0000..."
assert not verify_receipt(tampered) # False!验证失败
任何字段被改动 → 签名失效——这就是密码学的力量。
七、链式收据:让"删除历史"也不可行#
Securing AI Agents 强调哈希链:
def chain_receipts(receipts: list[dict]) -> list[dict]:
"""让每张收据指向上一张"""
prev_hash = None
for i, r in enumerate(receipts):
r["sequence"] = i
r["previous_receipt_hash"] = prev_hash
# 重新签名(因为 payload 变了)
canonical = canonicalize({k: v for k, v in r.items() if k != "signature"})
sig = signing_key.sign(canonical).signature
r["signature"]["sig"] = b64url_nopad(sig)
prev_hash = sha256_canonical(r)
return receipts
安全性:
要偷偷删一张收据,攻击者必须修改所有后续收据的
previous_receipt_hash这需要重新签名所有后续收据 → 需要私钥 → 在 HSM(硬件安全模块)里 → 不可能
八、人类审批收据(高风险动作)#
Securing AI Agents 提到了 human.approval.v1 收据——让人类审批也走同一个签名流程:
{
"type": "human.approval.v1",
"approver_id": "alice@contoso.com",
"action_digest": "sha256:...",
"timestamp": "2026-04-25T14:30:00Z",
"signature": {...}
}
绑定规则:Agent 动作收据的 action_digest 必须等于审批收据的 action_digest——否则就是 Agent 擅自行动。
典型拒绝场景:
篡改:审批被改了
Confused Deputy:用 A 的审批跑 B 的动作
Replay:用过期审批再跑一次
过期:政策版本变了,审批失效
九、收据能证明什么、不能证明什么#
Securing AI Agents 给出了一个重要的边界声明:
| 收据能证明 | 收据不能证明 |
|---|---|
| 归因:某个密钥签了某个 payload | 动作的正确性 |
| 完整性:payload 没被改 | 是否符合政策 |
| 顺序:这张在另一张之后 | 输入是否真实 |
| 私钥持有者是不是"应该"用它 |
核心:收据是"审计的基础设施",不是"治理本身"。
十、生产部署清单#
Securing AI Agents 总结了生产部署清单:
# 生产 Checklist
1. 把签名密钥从开发者机器移到 Vault(Azure Key Vault / AWS KMS / HSM)
2. 把公钥通过 JWK Set 发布到公开 URL
3. 定期把链头锚定到外部(Sigstore Rekor / RFC 3161 TSA)
4. 收据存储到不可变存储(Azure immutability policies / S3 Object Lock)
5. 设计保留策略:~500 字节/收据,10K 调用/天 ≈ 1.8 GB/年
6. 明确写出"收据不覆盖什么",和输入验证、政策、身份结合
十一、未来方向#
Securing AI Agents 列出了下一代方向:
选择性披露(Merkle 树字段承诺)
收据撤销(短生命周期密钥 / 透明日志)
双签 / 分票:执行前 + 执行后双签
跨实现一致性测试:用共享测试向量
后量子迁移:
signature.alg = ML-DSA-65双签过渡
十二、动手:完整的 Agent 收据系统#
class AuditedAgent:
"""每个工具调用都签发收据的 Agent"""
def __init__(self, agent_id: str, signing_key, policy_id: str):
self.agent_id = agent_id
self.signing_key = signing_key
self.verify_key = signing_key.verify_key
self.policy_id = policy_id
self.receipts = []
self.last_receipt_hash = None
def call_tool(self, tool_name: str, args: dict, result: dict) -> dict:
# 执行工具
# ...
# 签发收据
receipt = self._issue_receipt(tool_name, args, result)
self.receipts.append(receipt)
self.last_receipt_hash = sha256_canonical(receipt)
return receipt
def _issue_receipt(self, tool_name, args, result) -> dict:
payload = {
"type": "agent.tool_call.v1",
"agent_id": self.agent_id,
"tool_name": tool_name,
"tool_args_hash": sha256_canonical(args),
"result_hash": sha256_canonical(result),
"policy_id": self.policy_id,
"timestamp": datetime.utcnow().isoformat() + "Z",
"sequence": len(self.receipts),
"previous_receipt_hash": self.last_receipt_hash,
}
canonical_bytes = canonicalize(payload)
signature = self.signing_key.sign(canonical_bytes).signature
return {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature),
"public_key": b64url_nopad(bytes(self.verify_key)),
}
}
def export_audit_trail(self) -> list[dict]:
"""导出完整审计链"""
return self.receipts
十三、关键要点回顾#
| 机制 | 作用 |
|---|---|
| Ed25519 | 签名 + 验证算法 |
| JCS (RFC 8785) | 序列化确定性 |
| SHA-256 | 哈希 |
| 哈希链 | 顺序保证 |
| Vault 存储私钥 | 防私钥泄露 |
| 公开公钥 | 独立验证 |
核心思想:签名收据让 Agent 的每个动作独立可验证、不可篡改、可携带。
重要边界:收据证明"是谁做了什么",但不证明"做的对不对"。
十四、参考资源#
课程章节:Securing AI Agents
微软 Agent Governance Toolkit
PyNaCl:https://pynacl.readthedocs.io/
nobulex Python SDK:
pip install nobulexRFC 8032(Ed25519)、RFC 8785(JCS)、RFC 6962(证书透明度)
十五、课程总结#
到这里你已经完成了 src-001(Microsoft AI Agents for Beginners)的全部 19 章核心内容——加上 src-002(HF Agents Course)的核心模块。
💡 想知道怎么把这些能力串联成可观测、可恢复、可重试的工程化工作流?见 L23 工作流工程实战。
💡 想知道怎么主动攻击/防御你的 Agent?见 L26 Prompt 注入与对抗性防御。
你现在拥有的能力:
能向团队讲清楚:Agent vs Chatbot 的本质、ReAct 循环、Function Calling、Multi-Agent 协作、MCP/A2A/NLWeb 协议、Context Engineering、记忆、Planning、元认知、生产部署、安全、可观测性
能设计架构:从单 Agent 到多 Agent 协作,从本地到云端,从原型到生产
能选型:LangGraph / AutoGen / CrewAI / smolagents / MAF / Semantic Kernel——不同场景选不同框架
能落地:写代码、调工具、接 RAG、配记忆、做评估、上监控
下一步:
做项目:找一个真实业务场景,从头到尾做一个 Agent
看源码:挑一个框架(推荐 LangGraph 或 MAF),读它的核心源码
写学习记录:把每节课的核心心得用自己的语言复述
参与社区:HF Discord / Microsoft Foundry Discord / LangChain Discord
最后一句话——
Agent 工程不是关于"用什么框架",而是关于"如何让 LLM 真正解决用户问题"。框架会变、模型会变、协议会变,但"理解问题、设计可靠流程、持续评估改进"的能力是永恒的。
继续学、继续做、继续讲——你会越来越懂 Agent 的。
1. 根据本课的边界声明,一张签名收据(加密收据)能直接证明的是下列哪一项?
2. 哈希链机制主要防住的是下列哪一种攻击?
3. 签收据前为什么必须用 JCS(RFC 8785)做规范化序列化?