如果让你设计IPSec,你会怎么做?
真正理解一套协议,最好的方法之一,是暂时忘掉它现有的名字,重新面对它最初要解决的问题。
上一篇中,我们已经明确了IP协议的职责边界。
IP协议能够完成寻址、路由和数据包转发,让数据跨越不同网络到达目的地。
但当通信双方通过不可信网络交换数据时,普通IP通信无法自动解决四个问题:
数据可能被窃听;
数据可能被篡改;
通信身份可能被伪造;
历史报文可能被重放。
那么,假设现在还没有IPSec。
如果让我们从零开始设计一套安全通信机制,应该从哪里开始?
一、第一反应:把数据加密

假设Alice和Bob需要通过Internet通信
为了防止通信内容被中间人看到,最直接的想法就是:
把数据加密。
例如,Alice在发送数据之前,使用某种对称加密算法把明文转换成密文:
攻击者即使截获数据,也只能看到无法直接理解的密文。
这个思路没有错。
但只要继续向下思考,就会遇到第一个问题:
Alice用什么密钥加密?Bob又用什么密钥解密?
对称加密要求通信双方持有相同或相互对应的密钥。
如果Bob没有正确的密钥,他就无法还原Alice发送的数据。
于是,安全通信的第一个真正问题出现了:
通信双方如何获得共同的密钥?
二、不能直接把密钥发出去
一种看起来最简单的做法是,由Alice生成一把密钥,然后通过网络发送给Bob:

但这条网络本身就是不可信的。
如果攻击者能够截获后续通信,他同样可能截获这把密钥。
一旦密钥泄露,后续使用再强的加密算法,也无法继续保证通信安全。
因此,问题不能简单地表述为:
如何把密钥发送给对方?
真正的问题是:
如何让通信双方在不直接传输最终密钥的情况下,获得共同的秘密?
这就是密钥交换要解决的问题。
三、如何在不可信网络中建立共同秘密
Alice和Bob可以通过网络交换一部分公开信息,同时各自保留一部分不公开的信息。
双方利用:
公开交换的信息;
自己掌握的私密信息;
分别进行计算,最后得到相同的共享秘密。
整个过程中,最终得到的共享秘密并不会直接出现在网络中。
可以将这个过程简化为:

攻击者可以看到公开交换的信息,却不能轻易从这些信息中还原双方各自保留的秘密,也就难以直接计算出最终共享结果。
Diffie-Hellman密钥交换的核心思想正是如此。
需要注意的是,双方得到的通常不是直接拿来加密所有业务数据的一把固定密钥。
系统还会基于共享秘密以及本次协商中的其他信息,进一步派生出不同用途的密钥材料。
这些密钥可以分别用于:
保护协商过程;
加密业务数据;
校验数据完整性;
建立新的安全状态。
到这里,我们似乎已经解决了密钥问题。
但实际上,新的问题马上出现了。
四、有了共同秘密,对面就一定可信吗

假设Alice准备与Bob交换公开参数。
攻击者Eve位于两者之间:
Eve拦截了Alice发给Bob的信息,并把自己的信息交给Bob。
同时,她又拦截Bob发给Alice的信息,并把另一份自己的信息交给Alice。
最终可能形成:
Alice确实建立了一个加密通信过程。
Bob也确实建立了一个加密通信过程。
但他们分别与Eve建立了共同秘密,而不是彼此建立共同秘密。
这说明:
密钥交换可以帮助双方建立共同秘密,却不能自动证明通信对端的真实身份。
因此,安全通信还需要解决第二个问题:
如何确认对面确实是预期的通信方?
这就是身份认证。
通信双方需要使用某种事先能够信任的身份依据,证明自己不是攻击者伪装的。
这种依据可以是:
双方预先约定的秘密;
由可信机构签发的数字证书;
其他能够证明身份的认证材料。
身份认证解决的是“对面是谁”。
密钥交换解决的是“如何建立共同秘密”。
两者相关,但不是同一个问题。
五、身份确认之后,双方还要统一规则

假设Alice和Bob已经完成身份认证,也建立了共同秘密。
他们仍然不能立刻开始传输业务数据。
因为双方还需要对许多问题达成一致:
使用什么加密算法;
使用什么完整性保护方式;
使用多长的密钥;
安全状态可以使用多久;
密钥什么时候需要更新;
是否启用防重放;
出现异常时如何删除旧状态。
例如,Alice只支持算法A,而Bob只支持算法B,那么双方即使身份真实,也无法按照同一套规则处理数据。
因此,安全通信还需要一个协商过程:
如果双方能够找到共同支持的一组参数,就可以继续建立通信。
如果没有任何共同参数,协商就会失败。
这说明,安全通信并不是简单地“选一种算法然后加密”。
在真正传输数据之前,双方必须先确定一整套共同规则。
六、需要一个专门负责协商的机制
到这里,我们已经遇到了多个彼此相关的问题:
如何建立共同秘密;
如何验证通信身份;
如何选择双方都支持的算法;
如何确定密钥和状态的生命周期;
如何更新或删除失效的安全状态。
如果让每个应用程序分别实现这些过程,不仅工作量巨大,而且容易产生不一致和安全缺陷。
因此,需要一个专门负责协商的协议。
它不直接传输真正的业务数据,而是在业务数据开始受到保护之前,先帮助双方完成:
身份认证;
密钥交换;
参数协商;
安全状态建立;
安全状态更新和删除。
这个专门的协商机制,就是IKE。
可以把IKE理解为IPSec中的谈判者。
它负责让双方在不可信网络中先谈好:
对面是谁、采用什么规则、使用什么密钥,以及这些规则可以使用多久。
但IKE完成协商之后,还有一个问题:
协商出来的结果应该保存在哪里?
七、协商结果必须形成安全状态
假设Alice和Bob已经确定:
使用哪种算法;
使用哪些密钥;
状态有效多长时间;
如何识别重复报文;
如何处理发送和接收的数据。
这些结果不能只存在于一次协商消息中。
后续每一个业务数据包到来时,系统都需要重新使用这些参数。
因此,操作系统或网络设备必须把协商结果保存下来,形成一份可以持续使用的安全状态。
这份状态至少需要记录:
使用的算法;
对应的密钥材料;
状态的有效期;
当前序列号;
防重放相关信息;
与哪一方向的通信对应。
这类安全状态被称为安全关联,也就是SA。
可以暂时把SA理解为:
系统处理后续数据包时所依据的一组安全参数和运行状态。
IKE负责协商和建立SA。
后续数据处理机制则按照SA中的参数持续工作。
八、是不是所有数据包都应该受到保护
假设系统已经拥有了可用的安全状态。
接下来是否应该把所有IP数据包全部交给IPSec处理?
答案并不是永远如此。
一台主机或网关可能同时处理多种通信:
访问内部生产网络
访问公共网站
发送DNS请求
连接远程管理服务器
访问本地网段设备
其中,有些流量需要通过IPSec保护,有些流量可以正常转发,还有一些流量可能根本不应该被允许。
因此,系统在处理数据包之前,还必须回答:
这个IP数据包是否属于需要保护的通信?
这就需要一套安全策略。
安全策略负责描述:
哪些流量必须受到保护;
哪些流量可以直接通过;
哪些流量应当被丢弃。
它不负责完成加密,也不负责协商密钥。
它只负责做出选择:
IPSec的安全策略
因此,安全策略解决的是“保护谁”。
安全关联解决的是“按照什么状态进行保护”。
二者也不是同一个概念。
九、真正的业务数据由谁保护
到这里,双方已经完成了大部分准备工作:
身份已经得到确认;
共同秘密已经建立;
安全参数已经协商;
安全状态已经保存;
哪些流量需要保护已经确定。
接下来,真正的业务数据终于可以开始传输。
但IKE不负责处理这些数据。
IKE完成的是协商。
真正持续处理IP数据包的,是数据保护机制。
它需要根据已经建立的安全状态,对符合条件的数据包执行:
加密;
完整性保护;
序列号管理;
防重放处理;
必要的封装与解封装。
在IPSec体系中,承担这项核心任务的主要机制是ESP。
因此可以做一个初步区分:
IKE
负责建立和维护安全状态
SA
保存安全参数和运行状态
安全策略
决定哪些数据包需要保护
ESP
按照安全状态处理真正的业务数据
此时,这些名称不再是突然出现的术语。
它们分别对应了安全通信设计过程中无法绕开的具体问题。
十、回头再看,我们已经推导出了IPSec
推导PSec的过程
如果重新回顾整个设计过程,会发现IPSec并不是从几个协议名称开始的。
它是从一连串问题中逐步产生的:
最终自然形成了IPSec的几个核心部分:
IKE负责协商;
SA保存安全状态;
安全策略决定流量如何处理;
ESP负责保护业务数据。
这也说明:
IPSec不是一个孤立的加密算法,也不是一条简单的加密隧道,而是一套围绕安全IP通信建立起来的协议与状态体系。
总结
从零开始设计安全通信时,最先想到的往往是加密。
但继续推导就会发现,安全通信真正需要解决的远不止加密:
双方如何建立共同秘密;
如何确认通信对端身份;
如何协商共同支持的安全规则;
如何保存并维护协商结果;
如何决定哪些流量需要保护;
如何持续处理真正的业务数据。
为了解决这些问题,IPSec逐步形成了不同职责的组件:
IKE:负责协商
SA:保存安全状态
安全策略:决定哪些流量需要保护
ESP:处理并保护业务数据
这些组件并不是为了增加协议复杂度。
每一个组件的存在,都是为了解决前一个机制无法独立解决的新问题。
下一篇,我们将不再只从问题出发,而是站在系统架构的角度,把这些组件放到同一张图中:
安全策略、IKE、SA和ESP之间,到底如何协作完成一次安全通信?