AI 学习资料 · AI 智能体入门(精选 9 课)
第 18 课 用加密收据保护 AI 智能体
编者注:本课原文含 Python 代码、JSON 示例和两张用代码绘制的流程图,本库删去代码,每处用一句话说明;以动手写代码为主的“练习题”一节也未收录。完整内容见文末链接的英文原版。
简介
本课将涵盖:
- 为什么 AI 智能体的审计记录对合规、调试和信任至关重要。
- 什么是加密收据,它和不带签名的日志有什么区别。
- 如何用纯 Python 为智能体的一次工具调用生成签名收据。
- 如何离线验证收据、发现篡改。
- 如何把收据串成链,使删除或调换其中任何一张都会让链条断开。
- 收据能证明什么,以及明确不能证明什么。
学习目标
学完本课,你将知道如何:
- 识别哪些失效情形让我们需要为智能体的行为提供加密溯源。
- 对规范化的 JSON 内容生成 Ed25519 签名收据。
- 只用签名者的公钥,独立验证一张收据。
- 对改动过的收据重新运行验证,发现篡改。
- 构建一串用哈希串联的收据,并解释这条链为什么重要。
- 认清收据能证明的(归属、完整性、顺序)和不能证明的(行为是否正确、策略是否合理)之间的界线。
问题:智能体的审计记录
假设你为 Contoso Travel 部署了一个 AI 智能体。这个智能体读取客户的请求,调用航班 API 查询可选航班,并代表客户订座。上个季度,它处理了 5 万笔预订。
今天来了一位审计员。他先问了一个简单的问题:“给我看看你的智能体做了什么。”
你把日志文件交给他。审计员看完后问了一个更难的问题:“我怎么知道这些日志没被改过?”
这就是审计记录的难题。目前大多数智能体部署依赖的是:
- 应用日志:由智能体自己写入,任何能访问文件系统的人都能修改。
- 云日志服务:在平台层面能发现篡改,但前提是审计员信任平台运营方。
- 数据库事务日志:适合记录数据库的变更,但不适合记录任意的工具调用。
这些办法都没法在“审计员不必信任任何人”(你、你的云服务商、你的数据库厂商)的前提下回答审计员的问题。对内部使用来说,这种信任通常可以接受;但对受监管的业务(金融、医疗,以及任何受欧盟《人工智能法》约束的业务)来说,就不行了。
加密收据解决这个问题的办法,是让智能体的每一个行为都能被独立验证。审计员不需要信任你,只需要你的公钥和收据本身。
什么是加密收据?
收据是一个 JSON 对象,记录智能体做了什么,并附有数字签名。
图略:收据的处理流程。智能体调用工具,构建收据内容,按 RFC 8785 把 JSON 规范化,用 Ed25519 对规范化后的字节签名,得到带签名的收据;审计员离线验证,签名有效就构成防篡改的证据,无效则拒收这张收据(来源:英文原版)
代码略:一张最简收据的 JSON 示例,依次记录收据类型、智能体 ID、工具名称、工具参数的哈希、工具结果的哈希、所依据的策略 ID、时间戳、序号、上一张收据的哈希,以及签名对象(算法、签名值和公钥)。
起作用的是三个特性:
-
签名。收据由智能体的网关用 Ed25519 私钥签名。任何持有对应公钥的人都能离线验证签名。篡改任何一个字段,签名都会失效。
-
规范编码。签名之前,收据先按 JSON 规范化方案(JCS,RFC 8785)序列化。这保证了两个不同的实现,只要生成的收据在逻辑上相同,输出的字节就完全一样。如果不做规范化,不同的 JSON 序列化工具会对同样的内容生成不同的签名。
-
哈希串联。
previous_receipt_hash字段把每张收据和前一张连接起来。删除或调换一张收据,会破坏它之后的每一张收据。即使有人绕过了某张收据的签名,篡改也会在整条链的层面暴露出来。
这三个特性合在一起,提供三项保证:
- 归属:是这把密钥签署了这份内容。
- 完整性:内容自签名以来没有改变。
- 顺序:在链中,这张收据排在那张收据之后。
用 Python 生成收据
生成收据不需要专门的库。所需的密码学基础组件随处可得,逻辑只要几十行 Python 代码。
原课程配套的笔记本练习演示了完整流程,下面是精简版:
代码略:生成一对 Ed25519 签名密钥,把工具参数和工具结果分别规范化后取 SHA-256 哈希,与智能体 ID、工具名称、策略 ID、时间戳、序号等字段一起组成收据内容,再对规范化后的字节签名,最后把算法、签名值和公钥作为签名对象附到收据上。
这就是整个签名流程。笔记本里的练习会逐步讲解每一步。
验证收据、发现篡改
验证是签名的逆操作:
代码略:定义一个验证函数,取出收据中的签名对象并检查算法,把除签名以外的字段重新规范化成字节,再用收据里附带的公钥验证签名,通过返回“真”,否则返回“假”。
这个函数接收一张收据,签名有效就返回 True,否则返回 False。不需要联网,不依赖任何服务,也不需要信任任何第三方。
为了演示如何发现篡改,笔记本会带你:
- 生成一张有效的收据,确认它能通过验证。
- 修改
tool_args_hash字段中的一个字节。 - 重新验证,看到验证失败。
这就是收据能让篡改暴露的实际演示:任何修改,无论多小,都会破坏签名。
为多步骤智能体串联收据
一张签名收据保护一次行为,一串收据保护一系列行为。
图略:收据链示意。收据 0(起始收据)、收据 1、收据 2、收据 3 依次相连,每张收据都用
previous_receipt_hash指回前一张(来源:英文原版)
每张收据都记录前一张收据的哈希。攻击者要想悄悄删掉收据 2,就必须:
- 修改收据 3 的
previous_receipt_hash字段(这会破坏收据 3 的签名),或者 - 为改过的收据 3 伪造一个新签名(这需要智能体的私钥)。
如果私钥保存在硬件密钥库中,并且你在每张收据上都公布公钥,那么这两种攻击都无法在不被发现的情况下完成。
笔记本会带你:
- 构建一条包含三张收据的链。
- 验证每张收据的
previous_receipt_hash都与前一张收据的实际哈希一致。 - 篡改中间的一张收据,看到链条恰好在那里断开。
这样,你就得到了一份外部审计员无需信任你、也能自行验证的审计记录。
收据能证明什么(以及不能证明什么)
这是本课最重要的一节。收据很强大,但它的能力有边界。
收据能证明三件事:
- 归属:某把特定的密钥签署了某份特定的内容。
- 完整性:内容自签名以来没有改变。
- 顺序:在哈希链中,这张收据排在那张收据之后。
收据不能证明:
- 正确性:智能体的行为是不是正确的行为。一个错误的答案,同样可以得到一张干干净净的签名收据。
- 符合策略:
policy_id引用的策略是否真的被检查过,或者检查的话是否会允许这个行为。收据记录的是声称了什么,而不是实际执行了什么。 - 密钥之外的身份:收据说的是“这把密钥签署了这份内容”,而不是“这个人授权了这件事”。要把密钥和某个人或某个组织对应起来,需要另外的身份基础设施(目录、公钥登记表等)。
- 输入是否真实:如果智能体收到了被操纵的提示词并照着做了,收据会如实记录这个行为。收据处在输入校验的下游,不能代替输入校验。
这条边界之所以重要,有两个原因:
- 它告诉你收据能用来做什么:让智能体的行为可审计、篡改可被发现,即使跨越组织边界也一样。
- 它告诉你还需要哪些额外的防护层:输入校验(见第 6 课)、策略执行(下文简要介绍),以及身份基础设施(不在本课范围内)。
一个常见的误区是以为“我们有收据”就等于“我们有治理”。并非如此。收据只是基础,治理是你在这个基础上搭建的整套体系。
证明人类批准了这个确切的操作
上面第 3 点值得单独讲一讲:操作收据说的是“这把密钥签署了这份内容”,从来不会说“有人授权了这件事”。对于高风险操作(退款、删除、电汇),治理框架越来越多地要求的,恰恰就是缺失的这句话。而用本课已经搭好的基础组件,就能生成这样的证明。
后续的笔记本新增了第二种收据类型 human.approval.v1,外层结构与本课的收据相同(一份带类型的内容,对其规范化后的 JCS 字节做 Ed25519 签名,signature 对象放在被签名的字节之外)。在执行之前,由一位具名的审批人对完整的规范化操作及其摘要签名;智能体的操作收据则带上同一个操作摘要,以及 parent_approval_ref 字段,也就是那份审批收据的 receipt_hash,这与前面链中 previous_receipt_hash 的约定相同。同一个 verify_chain 函数分别依据两套各自固定的公钥登记表(审批人的公钥和智能体的公钥)来验证这两份收据,所以代码路径是共用的,但授权来源始终是分开的。
这样得到的特性,严谨地表述是:人类批准了这个确切的操作,而智能体执行的恰好就是这个被批准的操作。让这一特性真正成立、而不只是口头声称的,是笔记本里那些“应当被拒绝”的测试用例:
- 常规的几类:篡改、“混淆代理人”(confused deputy)、重放、任何一方伪造密钥、格式错误的输入;
- 授权已过时:签名仍然能验证通过,但因为策略版本变了、审批人的密钥已被轮换出固定登记表,或者审批在执行前已经过期,所以照样拒绝;
- 摘要被替换:一份签名有效的操作收据,指向一份真实的审批,但那份审批绑定的是另一个规范化操作。
每种失败都会给出不同的拒绝理由,审计员看到拒绝记录,就能分辨是授权过时了,还是执行的操作变了。笔记本教给你的规则是:一份签了名的审批本身并不构成授权。只有在执行时,两份收据仍然绑定到同一个规范化操作上,授权才成立。人类审批收据是本课为教学而组合出来的设计,并不是 draft-farley-acta-signed-receipts 草案中定义的收据类型。
用于生产环境的参考
本课的 Python 代码有意写得很精简,方便你逐行阅读、完全弄懂发生了什么。在生产环境中,你有两种选择:
-
直接在密码学基础组件上构建。前面看到的 50 行代码已能满足很多场景。PyNaCl(Ed25519)和
jcs包(规范化 JSON)都是维护良好、经过审计的库。 -
使用面向生产的收据库。有几个开源项目实现了同样的模式,并提供了更多功能(密钥轮换、批量验证、JWK 集分发、与策略引擎集成):
- 本课的签名流程采用了一份独立的 IETF 互联网草案(
draft-farley-acta-signed-receipts,第 02 版)中关于 JCS 和签名范围的约定。本课用于教学的扁平收据,与该草案{payload, signature}的外层结构不同,不应视为该草案的合规实现。该草案发布了一套共享的一致性测试(agent-governance-testvectors),供以其传输格式为目标的实现使用。 - Microsoft Agent Governance Toolkit 把收据与基于 Cedar 的策略决策结合在一起,端到端的示例见该仓库的教程 33。
protect-mcp(npm)和@veritasacta/verify(npm)这两个包提供了基于 Node 的收据签名和离线验证实现,用于给任意 MCP 服务器加上防篡改的审计记录。其中包括“暂停待共同签名”的流程:被暂停的操作会发出一份绑定了操作摘要的审批收据(桌面端流程由 WebAuthn 支撑),与上面人类授权笔记本采用的审批收据模式相同。- nobulex Python SDK 在 Python 中实现了同样的 Ed25519 加 JCS 签名模式,集成了 LangChain 和 CrewAI,附有公开的交叉验证测试向量,并通过 OWASP PR #2210 贡献了一份合规对照。
- 本课的签名流程采用了一份独立的 IETF 互联网草案(
自己动手写还是用现成的库,就像自己写 JWT 库还是用经过测试的库一样:两种做法都合理。用库省时间,也缩小了需要审计的范围;从零写则逼着你弄懂每一个基础组件。本课教的是从零写的路线,这样无论你最后选哪种,都有扎实的基础。
知识检测
先检验一下自己的理解。
1. 收据用智能体的 Ed25519 私钥签名,审计员只有公钥。审计员能离线验证这张收据吗?
答案: 能。Ed25519 验证只需要公钥和被签名的字节,不需要联网,也不依赖任何服务。正是这一点,让收据适用于物理隔离的网络、多个组织之间,或彼此信任度不高的审计场景。
2. 攻击者修改了收据的 policy_id 字段,想声称这次操作适用的是一套更宽松的策略。签名是针对原始内容做的。验证时会发生什么?
答案: 验证失败。签名是对原始内容规范化后的字节计算的;修改任何字段都会改变这些字节,签名也就失效了。攻击者需要私钥才能生成新的有效签名,而他没有私钥。
3. 为什么收据里放的是 tool_args_hash 和 result_hash,而不是原始的参数和结果?
答案: 有两个原因。第一,收据可能需要在某些环境中存档或传输,而在这些环境里泄露原始内容(个人身份信息、业务数据)会有问题。用哈希能让收据保持小巧,内容保持私密;审计员可以核对哈希是否与另外保存的实际内容相符。第二,哈希的长度是固定的,无论输入和输出有多大,带哈希的收据大小都是有上限的。
4. previous_receipt_hash 字段把每张收据与前一张连接起来。如果攻击者悄悄删掉链中间的一张收据,哪些东西会失效?
答案: 被删收据之后的每一张收据。它们的 previous_receipt_hash 字段与实际的链对不上了(因为它们引用的那张收据已经不存在,或者链现在指向了另一张前序收据)。要掩盖这次删除,攻击者必须给之后的每张收据重新签名,而这需要私钥。
5. 一张收据顺利通过了验证。这能证明智能体的行为正确、合理或符合策略吗?
答案: 不能。一张有效的收据能证明三件事:归属(这把密钥签署了这份内容)、完整性(内容没有被改过)和顺序(这张收据排在那张之后)。它不能证明操作是正确的,不能证明 policy_id 中写的策略真的被检查过,也不能证明智能体遵守了每一条规则。收据让智能体的行为可审计,但不保证它一定正确。这是本课最重要的一条界线。
结论
加密收据给 AI 智能体提供了这样一种审计记录:
- 可独立验证:任何持有公钥的一方都能验证,不依赖任何服务。
- 篡改可见:任何修改都会让签名失效。
- 便于携带:一张收据就是一个小小的 JSON 文件,可以存档、传输,在任何地方验证。
- 符合标准:建立在 Ed25519(RFC 8032)、JCS(RFC 8785)和 SHA-256 之上,都是广泛使用的基础组件。
收据不能代替输入校验、策略执行或身份基础设施,而是这些防护层的基础。当你把智能体部署到受监管的业务、多组织协作的工作流,或任何不能假定将来的审计员会信任你的场合时,收据就是让审计记录诚实可信的办法。
最重要的一点:收据证明的是谁在什么时候说了什么,而不证明所说的内容是真的或对的。务必牢牢记住这个区别。这正是诚实的溯源体系与误导人的溯源体系之间的差别。
上线检查清单
当你准备好学以致用,把带收据签名的智能体部署到真实环境时:
- 把签名密钥从开发者的笔记本电脑上移走。使用 Azure Key Vault、AWS KMS 或硬件安全模块。给收据签名的私钥绝不能出现在源代码仓库里,也不能以明文形式存放在应用服务器上。
- 公布用于验证的公钥。审计员要靠它离线验证。标准做法是把一个 JWK 集放在一个约定的固定地址(well-known URL,RFC 7517),例如
https://your-org.example.com/.well-known/agent-keys.json。 - 在外部为链做锚定。定期把最新的链头哈希写入一个透明日志(Sigstore Rekor、RFC 3161 时间戳机构,或另一个内部系统),让外部各方能确认“这条链在那个时间点已经存在”。
- 以不可变的方式存储收据。只能追加写入的对象存储(启用不可变策略的 Azure Storage、AWS S3 Object Lock)能防止内部人员在存储层改写历史。
- 确定保留期限。许多合规制度要求保留多年。要为收据的增长做好规划(每张收据约 500 字节;一个每天调用 1 万次的智能体,一年约产生 1.8 GB)。
- 写清楚收据不覆盖什么。收据证明的是归属、完整性和顺序。你的运维手册应当明确列出,在你的治理体系中,还有哪些控制措施(输入校验、策略执行、限流、身份基础设施)与收据并行使用。
本课之外
本课讲的是单张收据的签名和用哈希串联的收据序列。随着你的治理体系逐步成熟,同样的基础组件还能组合出几种更高级的模式:
- 选择性披露。如果收据的各个字段分别做了密码学承诺(RFC 6962 风格的 Merkle 树),你就可以只向特定的审计员披露特定的字段,同时证明其余字段没有改变,而不必把它们公开。当同一张收据既要满足全面审计(要求完整),又要遵守 GDPR 这类数据最小化法规(要求审计员看到的越少越好)时,这一点很有用。
- 收据吊销。如果某把签名密钥泄露了,你需要一种办法,把这把密钥从某个时间点起签署的所有收据都标为不可信。标准做法有:使用短期有效的签名密钥并公布吊销列表,或者使用带吊销条目的透明日志。
- 双边/拆分签名的收据。有些实现把被签名的内容拆成执行前(
authorization_*)和执行后(result_*)两半,分别签名。当授权决定和观察到的结果由不同的参与方、或在不同时间产生时,这种做法很有用。它可以直接叠加在本课所讲的收据格式之上。 - 丰富收据内容。收据会封存你放进
result_hash的任何字节。现实中的内容往往比一次工具调用的结果更丰富:决策前的推理(模型的预测、考虑过的选项、证据及其完整程度、风险状况、问责链、关卡的判定结果)都可以放进去,由一张收据封存。这样收据格式本身保持精简,而内容的结构可以按各自领域的需要演进。 - 跨实现的一致性。同一种收据格式有多个独立实现(Python、TypeScript、Rust、Go),它们都用一套共享的测试向量互相验证。如果你自己实现,用公开的测试向量验证一遍,就能确认传输格式兼容。
- 迁移到后量子算法。Ed25519 目前用得很广,但不能抵御量子计算攻击。收据格式支持更换算法:需要迁移时,
signature.alg字段可以填ML-DSA-65(NIST 的后量子签名标准)。要为收据同时带两种签名的过渡期做好规划。
补充资源
- IETF 互联网草案:机器对机器访问控制的签名决策收据
- 负责任 AI 概述(Azure AI)
- RFC 8032:爱德华兹曲线数字签名算法(EdDSA)
- RFC 8785:JSON 规范化方案(JCS)
- RFC 6962:证书透明度(选择性披露收据所用的 Merkle 树构造)
- Microsoft Agent Governance Toolkit 教程 33:可离线验证的决策收据
- 本课收据格式的跨实现一致性测试向量(Apache-2.0)
- PyNaCl 文档(在 Python 中使用 Ed25519)
免责声明:本课中文最初由 AI 翻译工具 Co-op Translator 自动翻译,本库对照英文原文做了校订和删节。译文仍可能有错漏,与英文原版不一致之处,以英文原版为准。