Havenlon 执行控制工程 02|密码学能冻结数据,但冻结不了现实
在很多系统的代码里都能找到这样一段逻辑验证签名通过则接受接受则执行。这条链短、清晰、易于测试几乎是现代安全工程的默认写法。它成立的前提是一个很少被写进注释里的假设——只要真正的私钥持有者签署了这条消息就意味着他已经理解并同意后续将要发生的一切。这个假设在大多数时候是对的。但它承载的重量远超过数字签名本身承诺过的东西。签名解决的是一个非常明确、同时也非常重要的问题谁对哪一段数据做了密码学确认以及这段数据在确认之后有没有被改动。这已经足以支撑绝大部分信息系统。但从签名有效推导到执行正确中间跨过的不是一步而是好几个性质完全不同的问题。当软件只负责传递信息时这个区别可以忽略。当软件开始直接转账、部署、删除数据、修改权限、控制设备或者让一个 Agent 自主完成这些动作时它就必须被拆开看。因为密码学能够证明一个声明的来源却不会替系统判断现实此刻是否应该按照这个声明发生变化。一、签名建立的是数据关系不是现实判断考虑一条被合法签署的数据向账户 B 转账十万元。验证端能确认两件事。第一这个签名确实由对应私钥产生第二被签名的内容在签名之后没有被修改。如果有人把十万改成一百万或者把账户 B 换成账户 C验证会失败。这正是数字签名最核心、也最不可替代的价值。但同样这条数据签名无法回答另外一组问题账户 B 是不是签署者真正希望支付的对象这十万元是不是来自正确的业务上下文这条指令是不是两小时前生成、按当时的规则现在已经不该再执行这笔款是不是已经支付过一次签名成立时依赖的那些条件现在是否仍然成立签署者在按下确认之前看到的是不是完整的执行语义这些都不在签名算法的职责范围内。更准确的说法是签名建立的是主体、数据与完整性之间的密码学关系它并没有天然建立主体真实意图、当前环境与最终执行结果之间的正确性关系。这两层经常被同一个词覆盖然后被当成同一件事。二、谁签的与为什么签私钥能够证明的是某个主体拥有签名能力。它无法证明这个主体在签名的那一刻看到了完整信息、正确理解了信息、没有被误导、展示内容没有被替换、知道这次签名最终会触发什么动作以及同意所有后续执行条件。于是会出现一种在密码学上完全干净的失败一个合法签名来自一个完全正常的用户对应的却是一次错误操作。这不是密码学失效恰恰相反密码学可能工作得完全正确。出问题的是我们向它索取了它从未承诺提供的语义。这是 Alice 签的与这就是 Alice 真正想让系统做的事情从来不是同一句话。这一层区分是理解执行控制时必须首先拆开的概念。三、被签名的对象未必等于完整的执行语义还有一个更工程化的问题到底签了什么。表面上这问题很简单实际上它决定了签名保护到了执行链的哪一层。假设系统让用户签署的是一个请求编号——用户签的是我同意 93721 号请求。密码学可以非常可靠地证明这一点。但 93721 号请求最终对应什么它在数据库里可能包含金额、目标地址、资产类型、有效时间、网络、费用上限等一组字段。如果这些字段没有被直接或间接地绑定进被签名的对象那么签名和最终执行之间就多出了一个解释层。用户签的是一个引用系统执行的却是根据当前对这个引用的解释所生成的一笔具体动作。这中间隔着一次状态查询。数据库内容在此期间被改动了怎么办调用链上某个服务重新解释了这个请求怎么办签名只覆盖了高层对象而执行器又根据其他上下文补齐了参数怎么办在这些情况下签名依然完全合法但签了什么和最终做了什么已经不再严格等价。所以在高风险执行里值得反复确认的问题不是有没有签名而是被签名的对象是否足以完整、明确地约束后续的执行语义。这已经不是密码学问题而是协议对象的设计问题。四、五个不该被同一个词覆盖的对象要把这件事讨论清楚可以把一次动作至少拆成五个层次。签名Signature证明某个主体对某份确定数据做了密码学确认负责来源与完整性。意图Intent描述主体究竟希望发生什么向谁转账、转多少、使用什么资产、允许在什么条件下执行、哪些条件一旦变化就必须重新确认。它负责的是意图表达本身。上下文Context是这份意图产生时所处的环境业务流程、审批来源、设备身份、会话、调用路径、组织关系。同一个动作放在不同上下文里含义可能完全不同。状态State描述执行发生之前现实系统当下的样子余额是否变化、目标资源是否仍然存在、同一动作是否已经执行过、授权是否过期。状态是动态的而签名通常是对某一时刻数据的静态证明。执行Execution是真正改变状态的那一步——钱转出去数据被删除服务被重新部署权限被修改一个继电器被打开。这是整条链最终触碰现实的位置。这五者可以互相关联但不能互相替代。工程上最常见的坍缩就是把签名、意图和执行折叠成同一个概念。五、签名之后世界仍然在变化数字签名非常擅长证明过去发生过什么。而执行控制关心的往往是现在处于什么状态。假设一名负责人在上午十点签署了一条部署指令。当时测试已经通过集群健康目标版本正确没有正在处理中的故障。由于任务排队这条指令在二十分钟后才真正到达执行器而在这二十分钟里生产环境出现了异常某个依赖服务进入降级状态。签名仍然有效指令没有被篡改签署者身份也完全正确。但十点钟成立的判断并不能自动证明十点二十仍然成立。同类问题在资金、基础设施和设备控制中都存在一笔支付授权完成之后账户的风险状态可能发生变化一条设备指令签署之后设备可能已经切换到异常模式一个 Agent 获得授权之后外部世界又产生了新的状态。密码学可以冻结一段数据却不能冻结数据之外的现实。这是执行前的状态验证无法被历史签名替代的根本原因。六、合法签名甚至可能忠实地保护一个错误意图更棘手的情形是签署者本人就是被诱导的。攻击者不去偷私钥而是设法让真正的持有者签署一份恶意内容。用户确实签了私钥没有泄露整条密码学链没有任何异常。从传统验证的角度看这甚至是一笔非常干净的操作。社会工程、被污染的前端、供应链问题都可能导向这个结果。Agent 场景把这个问题放到了更显眼的位置。一个自动化主体完全可能拥有合法身份、合法凭据和合法的签名能力。如果它因为错误的上下文、被注入的提示、有偏差的规划或者被污染的外部数据生成了一条危险的意图它同样会对这份意图完成一次完全合法的签名。此时继续增强签名算法、延长密钥长度、更换更强的硬件都不解决问题。因为问题已经从签名是不是真的变成了被签名的这个决定本身是否应该进入执行链。密钥是一种证明能力不是现实世界的最终裁决权。把拥有密钥等同于拥有改变现实的权力是一个在人类操作时代成本很低、在自动执行时代成本会显著上升的简化。七、把签名放回它最擅长的位置以上这些并不意味着签名的重要性在下降。恰恰相反执行链越长、参与方越多签名反而越关键——只是它的位置需要被摆得更准确。签名非常适合固定责任节点谁表达了意图谁完成了审批谁作出了裁决哪台设备参与了验证某份证据来自哪里某个记录在生成之后有没有被改动。在实际设计执行边界时这些签名的价值在于它们让每一段责任变得可追溯、可归属、事后无法被单方面改写。但它们应该共同服务于一条更长的验证链而不是任何一个签名一出现就直接触发执行。一条更合理的顺序接近于签名证明主体与声明意图描述希望发生什么上下文限定这份意图为什么成立状态验证现实条件是否仍然满足执行控制综合这些事实决定这个动作能否继续靠近现实最后才轮到执行器真正动手。在这个结构里签名不再承担它无法承担的职责而是回到它最擅长的角色证明事实而不是替系统判断事实意味着什么。需要说明的是这套拆分并不消除风险。它做的是缩小攻击面、增加独立验证点、提高绕过成本并在最后一步保留拒绝执行的能力。任何一层都可能出错区别只在于出错之后是否还有下一层。八、回到那三行逻辑回到开头那段代码。验证签名通过则接受接受则执行——它的问题不在于任何一步是错的而在于三步之间被省略的东西。当一个系统开始讨论签名安全时最先被问的通常是算法是否安全私钥是否泄露安全元件是否可靠验证实现是否正确。这些问题都必须回答。但再往前一步还有另一组问题被签名的对象是否具备语义完整性这份签名属于哪一类协议对象——它是在表达意图还是在确认审批抑或是在证明一次执行结果它是否可能被另一个环节错误复用签名完成之后有哪些现实条件必须在执行前重新验证一旦这些问题被提出来数字签名就不再只是一个密码学原语它开始成为执行协议设计的一部分。也正是在这里意图必须成为一个独立、稳定、可被验证的对象——因为如果系统连用户究竟在授权什么都无法清晰表达再强的签名也只能忠实地保护一份语义模糊的数据。密码学不会替系统补充缺失的语义。它可以证明谁同意了哪一段数据却不能自动证明现实此刻应该因此发生什么。前者是签名的职责而后者需要在执行真正发生之前被单独判断一次。