课程首页 › Module 9 · 安全与工作流工程:让 Agent 可验证、可恢复、可审计 › L22

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 数字签名
发票编号序列号 + 时间戳
防伪码哈希链(每张票链上一张)
普通纸(小偷能写)签名收据(伪造不了)

核心区别:


三、加密收据是什么?#

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

安全性:


八、人类审批收据(高风险动作)#

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 擅自行动。

典型拒绝场景:


九、收据能证明什么、不能证明什么#

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 列出了下一代方向:


十二、动手:完整的 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 的每个动作独立可验证、不可篡改、可携带。

重要边界:收据证明"是谁做了什么",但不证明"做的对不对"。


十四、参考资源#


十五、课程总结#

到这里你已经完成了 src-001(Microsoft AI Agents for Beginners)的全部 19 章核心内容——加上 src-002(HF Agents Course)的核心模块。

💡 想知道怎么把这些能力串联成可观测、可恢复、可重试的工程化工作流?见 L23 工作流工程实战。

💡 想知道怎么主动攻击/防御你的 Agent?见 L26 Prompt 注入与对抗性防御。

你现在拥有的能力:

  1. 能向团队讲清楚:Agent vs Chatbot 的本质、ReAct 循环、Function Calling、Multi-Agent 协作、MCP/A2A/NLWeb 协议、Context Engineering、记忆、Planning、元认知、生产部署、安全、可观测性

  2. 能设计架构:从单 Agent 到多 Agent 协作,从本地到云端,从原型到生产

  3. 能选型:LangGraph / AutoGen / CrewAI / smolagents / MAF / Semantic Kernel——不同场景选不同框架

  4. 能落地:写代码、调工具、接 RAG、配记忆、做评估、上监控

下一步:

最后一句话——

Agent 工程不是关于"用什么框架",而是关于"如何让 LLM 真正解决用户问题"。框架会变、模型会变、协议会变,但"理解问题、设计可靠流程、持续评估改进"的能力是永恒的。

继续学、继续做、继续讲——你会越来越懂 Agent 的。

自测 · 检验是否真的掌握了

1. 根据本课的边界声明,一张签名收据(加密收据)能直接证明的是下列哪一项?

收据是对 payload 的签名,密码学上只能保证两点:某个密钥对该载荷做了签名(归因),以及载荷自签名后未被改动(完整性),哈希链再补上顺序。收据不判断动作是否符合业务规则和政策,也不验证输入本身是否真实,因此“正确性”“合规性”“输入可信”都超出它的证明范围。

2. 哈希链机制主要防住的是下列哪一种攻击?

每张收据里都带上一张收据的哈希指针,所以删掉中间一张后,后续收据的 previous_receipt_hash 会全部对不上,链因此断裂。而修改某个字段或伪造新收据,分别是靠 Ed25519 签名和私钥的独占性来防的,不是哈希链的功劳。

3. 签收据前为什么必须用 JCS(RFC 8785)做规范化序列化?

签名覆盖的是字节序列而非 JSON 对象本身,如果序列化结果不确定(键序、空格、换行、数字格式都可能变),同一个 payload 算出的哈希和签名就会不一致,验证方也会误判为篡改。JCS 规定了确定的键序与编码方式,从而保证签名方和验证方得到逐字节相同的内容。