26/CURVEZMQ
CurveZMQ
- 状态:稳定版
- 编辑:Pieter Hintjens ph@imatix.com
本文档描述了 CurveZMQ,这是一种用于在互联网上进行安全消息传递的协议。CurveZMQ 紧密基于 Daniel J. Bernstein 的 CurveCP,并为在 ZeroMQ over TCP 中使用进行了调整。CurveZMQ 的参考实现可在 curvezmq.org 找到。本文档描述了 CurveZMQ 的 1.0 版本。
另请参阅:https://rfc.zeromq.cn/spec:23/ZMTP, https://rfc.zeromq.cn/spec:25/ZMTP-CURVE。
前言
版权所有 (c) 2013 iMatix Corporation。
本规范是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证的条款重新分发和/或修改它;无论是许可证的第 3 版,还是(由您选择)任何后续版本。分发本规范是希望它会有用,但**不提供任何保证**;甚至不提供针对特定用途的适销性或适用性的默示保证。有关更多详细信息,请参阅 GNU 通用公共许可证。您应该已经 साथ本程序收到了 GNU 通用公共许可证的副本;如果没有,请参阅 https://gnu.ac.cn/licenses。
本规范是自由和开放的标准,受数字标准组织(Digital Standards Organization)的共识导向规范系统(Consensus-Oriented Specification System)管辖。
文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。
CurveZMQ 规范
CurveZMQ 是一种用于在互联网上进行安全消息传递的协议,它紧密遵循 CurveCP 安全握手。我们构建 CurveZMQ 的主要目的是为 ZeroMQ 应用程序提供安全性,但它也可以更广泛地使用。CurveZMQ 协议可以在我们构建 CurveZMQ 的主要目的是为 ZeroMQ 应用程序提供安全性,但它也可以更广泛地使用。libzmq API 之上工作,位于应用层;或者在 libzmq 内部,作为 ZMTP 3.0 传输层的安全机制。
目标
CurveZMQ 旨在提供与 CurveCP 相同的安全级别,尽管 UDP 和 TCP 存在差异。也就是说,它旨在防止窃听、欺诈性数据、篡改数据、重放攻击、放大攻击、中间人攻击、密钥窃取攻击、身份攻击以及某些拒绝服务攻击。
此外,它还旨在:
-
通过定义特定的命令语法和语义,确保一种实现发送的命令可以被另一种实现正确处理,从而提供使用任意语言编写的实现之间的互操作性。
-
可以作为堆栈中不同级别的插件机制使用,而不仅仅是作为传输层安全。这意味着 CurveZMQ 可以与任何传输方式一起使用,包括 ZMTP 的旧版本。
-
与 ZMTP 3.0 的可扩展安全模型兼容,该模型基于 IETF 的简单认证和安全层(Simple Authentication and Security Layer - SASL)。
-
如果给定语言支持 NaCl 或 libsodium,则易于实现。
安全性
CurveZMQ 使用 Curve25519 椭圆曲线,该曲线由 Daniel J. Bernstein 设计,旨在通过较短的密钥长度(256 位)实现良好的性能。该协议为每个连接建立短期会话密钥,以实现完美前向保密(perfect forward security)。会话密钥保存在内存中,并在连接关闭时销毁。CurveZMQ 还解决了重放攻击、放大攻击、MIM(中间人)攻击、密钥窃取、客户端识别以及各种拒绝服务攻击。这些特性继承自 CurveCP,稍后会解释。
用例
CurveZMQ 的两个主要用例是:
-
保护客户端和服务器之间的单跳连接,这是 CurveCP 的用例。对于此用例,我们将把 CurveZMQ 嵌入到传输层中,以便它适用于所有模式(发布-订阅、管道等)。
-
通过一个或多个不受信任的对等方端到端保护客户端和服务器,在传输层安全不足的情况下。对于此用例,我们将使用异步请求-回复模式将 CurveZMQ 构建到我们的应用层协议中。
此外,CurveZMQ 还可以用于通过带外传输(out-of-band transports)从一个对等方发送到另一个对等方的数据,例如电子邮件或文件传输,前提是两个对等方首先通过合适的传输方式执行初始握手。
CurveZMQ 的整体操作
客户端和服务器拥有长期永久密钥,对于每个连接,它们都会创建并安全地交换短期临时密钥。每个密钥都是一对公钥/私钥,遵循椭圆曲线安全模型。
要开始安全连接,客户端需要服务器的永久公钥。然后它生成一对临时密钥,并向服务器发送一个包含其短期公钥的 HELLO 命令。HELLO 命令对攻击者来说是无用的;它不标识客户端。
服务器接收到 HELLO 后,会生成自己的短期密钥对(一个连接总共使用四个密钥),并将这个新的私钥编码在一个“cookie”中,作为 WELCOME 命令发送回客户端。它还会发送其短期公钥,经过加密,只有客户端可以读取。然后它丢弃这对短期密钥。
在此阶段,服务器没有为客户端存储任何状态。它生成了一对密钥,以只有客户端能够读取的方式将其发送回客户端,然后就丢弃了。
客户端随后发送一个 INITIATE 命令,将 cookie 返回给服务器,并发送其永久公钥,该公钥作为“vouch”加密,只有服务器可以读取。对于客户端而言,服务器现已通过认证,因此它也可以在命令中发送元数据。
服务器读取 INITIATE 命令,现在可以认证客户端的永久公钥。它还会解包 cookie,获取连接的短期密钥对。对于服务器而言,客户端现已通过认证,因此服务器可以安全地发送其元数据。然后双方都可以发送消息和命令。
这个握手提供了多重保护,但主要是完美前向保密(perfect forward security)(即使你记录了使用短期密钥加密的数据,并在之后获取了永久密钥,也无法破解它)和客户端身份保护(客户端永久公钥不以明文发送)。
总体设计
CurveZMQ 协议有一个同步握手,随后是双向消息的异步交换。协议中的每个操作都是一个“命令”,由一个命令名称后跟一个命令体组成。命令名称为 8 个八位字节,用空格填充,而命令体具有依赖于命令的二进制编码。
CurveZMQ 包含以下命令:HELLO、WELCOME、INITIATE、READY、MESSAGE 和 ERROR。连接总是由客户端向服务器发送 HELLO 开始,服务器回应 WELCOME。
C:HELLO
S:WELCOME
在此交换之后,双方都拥有对方的公共临时密钥。然后客户端发送 INITIATE 命令,服务器回应 READY。
C:INITIATE
S:READY
在此交换之后,双方已相互认证并交换了连接的元数据。现在它们可以以 MESSAGE 命令的形式发送消息,顺序不限,且无需进一步同步。
C:MESSAGE | S:MESSAGE
任何一方都可以随时静默关闭其连接端,这也可能由于网络行为发生。这被视为软错误,断开连接的对等方可以在合适的间隔后**可以**重试连接。
要明确拒绝客户端连接,服务器**应**发送一个 ERROR 命令。
S:ERROR
这被视为硬错误,客户端**不应**尝试使用相同的凭据重新连接。客户端对等方**不应**向服务器发送 ERROR。
CurveZMQ 设计为一个黑盒,接受和产生格式正确的命令,并在侧面输出数据消息,并在内部为每个连接运行一个状态机。
在 CurveZMQ 架构中,客户端在连接之前**必须**知道服务器的公钥。服务器**可以**知道客户端的公钥,并且**可以**根据其密钥区分不同的客户端。这为我们提供了三种可能的安全模型:
-
服务器完全不检查客户端密钥。在这种情况下,客户端可以确定它们正在安全地与正确的服务器通信,但服务器将接受来自任何客户端的连接。这符合传统的互联网模型,其中浏览器安全地与网站通信以进行订购和发送信用卡信息。
-
所有客户端共享同一个公钥,服务器会检查该公钥。在这种情况下,对服务器的访问将限制在授权客户端。这符合公共基础设施上的私有网络模型。请注意,客户端公钥可能会被窃取,但除非攻击者也窃取了客户端的私钥,否则无法使用。
-
每个客户端都有自己的密钥,服务器会检查该密钥。在这种情况下,服务器可以根据客户端的认证身份授予访问权限。同样,攻击者可能会窃取客户端公钥,但除非也能窃取客户端的私钥,否则无法利用它做任何事情。
椭圆曲线加密依赖于在每次加密操作中混合一个唯一的数字。这个“一次性使用的数字”(“number used once”)或“nonce”由发送方选择,可以阻止重放攻击,并保护密钥免受密码分析。我们在 CurveZMQ 中使用两种 nonce。长 nonce 用于保护永久密钥,是来自良好随机数生成器的 16 个八位字节。短 nonce 用于保护临时密钥,是一个 8 个八位字节的序列号。
高层语法
以下 ABNF 语法从高层定义了 CurveZMQ 协议:
curvezmq = C:hello ( S:welcome | S:error )
C:initiate ( S:ready | S:error )
*message
; HELLO command, 200 octets
hello = %d5 "HELLO" hello-version hello-padding hello-client hello-nonce hello-box
hello-version = %x1 %x0 ; CurveZMQ major-minor version
hello-padding = 72%x00 ; Anti-amplification padding
hello-client = 32OCTET ; Client public transient key C'
hello-nonce = 8OCTET ; Short nonce, prefixed by "CurveZMQHELLO---"
hello-box = 80OCTET ; Signature, Box [64 * %x0](C'->S)
; WELCOME command, 168 octets
welcome = %d7 "WELCOME" welcome-nonce welcome-box
welcome-nonce = 16OCTET ; Long nonce, prefixed by "WELCOME-"
welcome-box = 144OCTET ; Box [S' + cookie](S->C')
; This is the text sent encrypted in the box
cookie = cookie-nonce cookie-box
cookie-nonce = 16OCTET ; Long nonce, prefixed by "COOKIE--"
cookie-box = 80OCTET ; Box [C' + s'](K)
; INITIATE command, 257+ octets
initiate = %d8 "INITIATE" initiate-cookie initiate-nonce initiate-box
initiate-cookie = cookie ; Server-provided cookie
initiate-nonce = 8OCTET ; Short nonce, prefixed by "CurveZMQINITIATE"
initiate-box = 144*OCTET ; Box [C + vouch + metadata](C'->S')
; This is the text sent encrypted in the box
vouch = vouch-nonce vouch-box
vouch-nonce = 16OCTET ; Long nonce, prefixed by "VOUCH---"
vouch-box = 80OCTET ; Box [C',S](C->S')
metadata = *property
property = name value
name = OCTET *name-char
name-char = ALPHA | DIGIT | "-" | "_" | "." | "+"
value = value-size value-data
value-size = 4OCTET ; Size in network order
value-data = *OCTET ; 0 or more octets
; READY command, 30+ octets
ready = %d5 "READY" ready-nonce ready-box
ready-nonce = 8OCTET ; Short nonce, prefixed by "CurveZMQREADY---"
ready-box = 16*OCTET ; Box [metadata](S'->C')
; ERROR command, 7+ octets
error = %d5 "ERROR" error-reason
error-reason = OCTET 0*255VCHAR
; MESSAGE command, 33+ octets
message = %d7 "MESSAGE" message_nonce message-box
message-nonce = 8OCTET ; Short nonce, prefixed by "CurveZMQMESSAGE-"
message-box = 17*OCTET ; Box [payload](S'->C') or (C'->S')
; This is the text sent encrypted in the box
payload = payload-flags payload-data
payload-flags = OCTET ; Explained below
payload-data = *octet ; 0 or more octets
命令大小不是命令编码的一部分,但被假定为命令的一个属性(换句话说,在实现编码或解码命令时是已知的)。
CurveZMQ 命令
我们解释了每个命令如何加密,每个命令的内容以及每个命令的语义。请注意,我们使用四对密钥:
- 客户端和服务器各自拥有一对永久密钥(公钥和私钥),分别称为 C 和 S。
- 客户端和服务器各自为连接生成一对临时密钥,分别称为 C' 和 S'。
我们使用 CurveCP 符号“Box X”表示一个加密盒,它将 X “从 C 到 S”地加密,意味着只有 C 可以创建该盒,只有 S 可以打开它。一个盒是从 C 到 S 的单向信息传输,发送方和接收方都可以确定传输是秘密的、安全的和真实的(如果它到达,这并不保证)。当 S 打开盒子时,它知道是 C 创建的。创建和打开盒子的实际步骤是:
- 使用 S 的公钥、C 的私钥和一个精心选择的 nonce 创建盒子。
- 使用 C 的公钥、S 的私钥和相同的 nonce 打开盒子。
CurveZMQ 使用与 CurveCP 相同的加密技术,因此密钥为 32 个八位字节(256 位),nonce 为 24 个八位字节。加密盒始终比其包含的明文数据大 16 个八位字节。这些大小不可配置;它们由底层加密库强制执行,并作为 CurveZMQ 实现的通用常量。未来版本中可能会改变。
HELLO 命令
CurveZMQ 连接上的第一个命令是 HELLO 命令。客户端在打开流连接后**应**发送一个 HELLO 命令。该命令**应**为 200 个八位字节长,并包含以下字段:
-
命令 ID,即 [5]“HELLO”。
-
CurveZMQ 版本号,**应**为两个八位字节 1 和 0。
-
抗放大填充字段。这**应**为 70 个八位字节,全部为零。这个填充字段确保 HELLO 命令大于 WELCOME 命令,这样伪造发送方 IP 地址的攻击者就无法利用服务器用响应数据淹没无辜的第三方。
-
客户端的公共临时密钥 C'(32 个八位字节)。客户端**应**为其创建到服务器的每个连接生成一对唯一的密钥。它在关闭连接时**应**丢弃此密钥对,并且**不得**将其私钥存储在永久存储中,也不得以任何方式共享。
-
客户端短 nonce(8 个八位字节)。该 nonce **应**隐式地以 16 个字符 @@“CurveZMQHELLO—"@@ 为前缀,形成用于加密和解密签名盒的 24 个八位字节的 nonce。
-
签名盒(80 个八位字节)。这**应**包含 64 个零八位字节,从客户端的临时密钥 C' 加密到服务器的永久密钥 S。
服务器**应**验证所有字段,并**应**拒绝并断开发送格式错误 HELLO 命令的客户端。
当服务器接收到有效的 HELLO 命令时,它**应**生成一对新的临时密钥,并将公钥和私钥都编码在 WELCOME 命令中,如下文所述。服务器**不应**保留这对临时密钥对,并且**应**在客户端以有效的 INITIATE 命令响应之前,为客户端保持最小状态。这可以防止拒绝服务攻击,即未经认证的客户端发送许多 HELLO 命令来消耗服务器资源。
请注意,客户端在 HELLO、INITIATE 和 MESSAGE 命令中使用了 8 个八位字节的“短 nonce”。该 nonce **应**是一个递增的整数,并且在连接内的每个命令中都是唯一的。客户端**不应**在一个连接中发送超过 2^64-1 个命令。服务器**应**验证客户端连接是否使用了正确递增的短 nonce,并且**应**断开重复使用短 nonce 的客户端。
WELCOME 命令
服务器**应**以 WELCOME 命令回应有效的 HELLO 命令。该命令**应**为 168 个八位字节长,并包含以下字段:
-
命令 ID,即 [7]“WELCOME”。
-
服务器长 nonce(16 个八位字节)。该 nonce **应**隐式地以 8 个字符“WELCOME-”为前缀,形成用于加密和解密 welcome 盒的 24 个八位字节的 nonce。
-
welcome 盒(144 个八位字节),它将服务器公共临时密钥 S'(32 个八位字节)和服务器 cookie(96 个八位字节)从服务器永久密钥 S 加密到客户端的临时密钥 C'。
请注意,服务器在 WELCOME 命令和创建 cookie 时使用 16 个八位字节的“长 nonce”。该 nonce 对于此服务器永久密钥**应**是唯一的。推荐的最简单策略是使用来自足够好的熵源的 16 个随机八位字节。
cookie 包含两个字段:
-
服务器长 nonce(16 个八位字节)。该 nonce **应**隐式地以 8 个字符 @@“COOKIE–"@@ 为前缀,形成用于加密和解密 cookie 盒的 24 个八位字节的 nonce。
-
cookie 盒(80 个八位字节),它包含客户端公共临时密钥 C'(32 个八位字节)和服务器私有临时密钥 s'(32 个八位字节),加密到并来自一个秘密的短期“cookie 密钥”。服务器在短时间间隔后(例如 60 秒)或客户端发送有效的 INITIATE 命令后,**必须**从内存中丢弃该 cookie 密钥。
服务器**应**为其发送的每个 WELCOME 命令生成一个新的 cookie。
客户端**应**验证所有字段,并**应**断开发送格式错误 WELCOME 命令的服务器。
INITIATE 命令
当客户端接收到 WELCOME 命令时,可以对其进行解密以接收服务器的临时密钥 S' 和 cookie,它必须将 cookie 发送回服务器。cookie 是服务器私有临时密钥 s' 的唯一记忆。
客户端**应**以 INITIATE 命令回应有效的 WELCOME 命令。该命令**应**至少为 257 个八位字节长,并包含以下字段:
-
服务器在 WELCOME 命令中提供的 cookie(96 个八位字节)。
-
客户端短 nonce(8 个八位字节)。该 nonce **应**隐式地以 16 个字符“CurveZMQINITIATE”为前缀,形成用于加密和解密 vouch 盒的 24 个八位字节的 nonce。
-
initiate 盒(144 个或更多八位字节),客户端通过它安全地将其永久公钥 C 发送给服务器。initiate 盒包含客户端永久公钥 C(32 个八位字节)、vouch(96 个八位字节)和元数据(0 个或更多八位字节),从客户端的临时密钥 C' 加密到服务器的临时密钥 S'。
-
vouch 本身包含两个字段:
客户端长 nonce(16 个八位字节)。该 nonce **应**隐式地以 8 个字符 @@“VOUCH—"@@ 为前缀,形成用于加密和解密 vouch 盒的 24 个八位字节的 nonce。该 nonce 对于来自此客户端永久密钥的所有 INITIATE 命令**应**是唯一的。一种有效的策略是使用来自足够好的熵源的 16 个随机八位字节。
-
vouch 盒(80 个八位字节),它将客户端的临时密钥 C'(32 个八位字节)和服务器永久密钥 S(32 个八位字节)从客户端永久密钥 C 加密到服务器临时密钥 S'。
-
元数据由属性列表组成,每个属性包括名称和值,作为大小指定的字符串。名称**应**为 1 到 255 个字符。大小为零的名称无效。名称的大小写(大写或小写)**不应**具有重要意义。值**应**为 0 到 2^31-1 个八位字节的不透明二进制数据。允许大小为零的值。值的语义取决于属性。值大小字段**应**为四个八位字节,采用网络字节序。请注意,此大小字段在内存中通常不会对齐。
服务器**应**验证所有字段,并**应**拒绝并断开发送格式错误 INITIATE 命令的客户端。
解密 INITIATE 命令后,服务器**可以**根据客户端的永久公钥 C 认证客户端。如果客户端未通过认证,服务器**不应**响应,只能关闭连接。如果客户端通过认证,服务器**应**发送一个 READY 命令,然后**可以**立即发送 MESSAGE 命令。
READY 命令
服务器**应**以 READY 命令回应有效的 INITIATE 命令。该命令**应**至少为 30 个八位字节长,并包含以下字段:
命令 ID,即 [5]“READY”。
-
服务器短 nonce(8 个八位字节)。该 nonce **应**隐式地以 16 个字符 @@“CurveZMQREADY—"@@ 为前缀,形成用于加密和解密 ready 盒的 24 个八位字节的 nonce。
-
ready 盒(16 个或更多八位字节)。这**应**包含与 INITIATE 命令中发送的元数据相同格式的数据,从服务器的临时密钥 S' 加密到客户端的临时密钥 C'。
-
客户端**应**验证所有字段,并**应**断开发送格式错误 READY 命令的服务器。
客户端**可以**验证元数据。如果客户端接受元数据,则**应**期望收到来自服务器的 MESSAGE 命令。
请注意,服务器在 HELLO、INITIATE 和 MESSAGE 命令中使用了 8 个八位字节的“短 nonce”。该 nonce **应**是一个递增的整数,并且在连接内的每个命令中都是唯一的。服务器**不应**在一个连接中发送超过 2^64-1 个命令。客户端**应**验证服务器连接是否使用了正确递增的短 nonce,并且**应**断开重复使用短 nonce 的服务器。
MESSAGE 命令
客户端在接收到有效的 READY 命令后**可以**发送 MESSAGE 命令。服务器在发送 READY 命令后**可以**发送 MESSAGE 命令。该命令**应**至少为 33 个八位字节长,并包含以下字段:
命令 ID,即 [7]“MESSAGE”。
-
短 nonce(8 个八位字节)。该 nonce **应**隐式地以 16 个字符“CurveZMQMESSAGES”(来自服务器)或“CurveZMQMESSAGEC”(来自客户端)为前缀,形成用于加密和解密 message 盒的 24 个八位字节的 nonce。
-
message 盒(17 个或更多八位字节)。这**应**包含消息数据,从发送方的临时密钥加密到接收方的临时密钥。
-
消息负载由一个标志字段(1 个八位字节)后跟零个或更多八位字节的负载数据组成。标志字段由单个八位字节组成,包含各种控制标志。位 0 是最低有效位(最右边的位):
位 7-1:保留。这些位保留供将来使用,并且**必须**为零。
-
位 0(MORE):后续还有消息。值为 1 表示后续还有更多消息。值为 0 表示后续没有更多消息。
-
接收方**应**验证所有字段,并**应**拒绝并断开发送格式错误 MESSAGE 命令的对等方。服务器**不应**发送 ERROR 命令来回应无效的 MESSAGE 命令。
ERROR 命令
服务器**应**通过向客户端发送 ERROR 命令来指示认证失败。服务器在其他失败情况下**可以**用 ERROR 响应,或者可以根据情况静默断开客户端连接。该命令**应**至少为 7 个八位字节长,并包含以下字段:
命令 ID,即 [5]“ERROR”。
-
错误原因,这是一个长度指定的字符串,包含 0 到 255 个 ASCII 字符。
-
CurveCP 定义了通过 UDP 发送消息的安全握手和流量控制,而 CurveZMQ 仅定义了通过连接流协议(如 TCP、IPC、SCTP 或类似协议)发送消息的安全握手。我们对 CurveCP 进行了以下重要修改:
与 CurveCP 的区别
INITIATE 命令 vouch 盒是 BoxC’,S,而不是 BoxC’,这是根据 Codes in Chaos 的建议进行的修改,以降低客户端伪装的风险。
-
我们使用术语“command”代替“packet”来表示通过网络发送的原子数据块。
-
HELLO 命令具有协议版本号(CurveCP 没有版本号)。
-
我们将 Cookie 重命名为 WELCOME,以与 ZMTP 3.0 中使用的其他机制保持一致。
-
在 INITIATE 命令中,我们不发送消息数据或主机名,但我们发送连接元数据,其中可以包括主机名和其他属性。
-
我们添加了一个 READY 命令,用于在安全建立后从服务器向客户端发送连接元数据。
-
在来自客户端的 INITIATE 和 MESSAGE 命令中,我们不发送客户端公共临时密钥,因为它在连接协议中是冗余的。
-
在 MESSAGE 命令中,负载可以是任意大小,从零到 2^63-1 个八位字节,而 CurveCP 将包限制在 UDP 帧内。
-
在 MESSAGE 命令中,负载具有帧格式,支持 ZMTP 3.0 的一些要求,特别是 Continuation 标志(“more”)。
-
在 MESSAGE 命令中,我们不使用 CurveCP 流量控制字段,因为流量控制由底层流协议处理。
-
CurveCP 没有明确的 ERROR 响应,也不告知客户端认证失败。
-
CurveCP 提供流(从包重组),而 CurveZMQ 提供原子消息。
-
CurveCP 是 TCP 的替代方案,它使用 UDP 消息在服务器和客户端之间创建安全连接。CurveCP 的安全性旨在抵御一组特定的攻击,我们将在后面解释。CurveCP 的加密核心是网络和密码学库(Networking and Cryptography library - NaCl),更广泛地用作 libsodium。NaCl 因其对速度、强度和简洁性的关注而意义重大。
CurveCP
CurveCP 于 2010 年 12 月 28 日在第 27 届 Chaos Communication Congress 上宣布,CurveCP 实现仍被认为是实验性的。
CurveCP 有两个主要功能:一是提供从发送方到接收方的可靠字节流;二是确保传输的数据是机密的、不可篡改的或伪造的。我们将只关注第二部分,它包括一个安全握手,随后是一个加密和认证机制。
总体设计
协议定义了明确的“客户端”和“服务器”角色,这反映了大多数架构的非对称性质。例如,大多数攻击集中在服务器而非客户端,因此 CurveCP 的防御措施更侧重于保护服务器而非客户端。CurveZMQ 继承了这一观点,但未来我们可能会转向更对称的防御措施。
CurveCP 使用椭圆曲线密码学来加密和认证每个包。它将数据放入一个加密盒中,该盒从发送方的私钥到接收方的公钥进行加密和认证。要创建和成功打开一个盒,NaCl 需要五个要素:
发送方和接收方的公钥,我们假设这些密钥事先以某种安全方式交换。公钥不是机密的,但任何交换机制本身必须安全可靠,防止欺诈,否则攻击者可以替换自己的密钥并执行中间人攻击。
-
两个相应的私钥,它们绝不以任何形式交换。
-
一个公共 nonce,或“一次性使用的数字”,由发送方选择。Nonce 可以阻止重放攻击,CurveCP 非常关注如何选择 nonce。总的来说,我们根据具体情况使用随机八位字节或递增整数。
-
NaCl(以及本文档)使用语法“Box S->R X”来定义一个加密盒,该盒将数据 X 从发送方 S 加密和认证到接收方 R。要创建盒子,我们需要 nonce、S 的私钥和 R 的公钥。要打开盒子,我们需要 nonce、R 的私钥和 S 的公钥。当我们成功打开盒子时,我们知道它是由 S 发送的,并且数据未被篡改。如果无法打开盒子,说明它已被篡改或非法创建。
CurveCP 中的密钥是 32 个八位字节(256 位),nonce 是 24 个八位字节。这些是 NaCl 提出的默认大小。这些大小被硬编码到 CurveCP 包格式中,CurveZMQ 也使用相同的大小。用户无法选择较弱的安全性。然而,我们期望未来版本的 CurveZMQ 使用更长的密钥大小。
每个对等方(客户端或服务器)都有一个永久密钥,该密钥事先为对方所知。CurveCP 在连接开始时使用永久密钥进行认证并安全地交换临时密钥(CurveCP 称之为“短期密钥”)。永久密钥从不用于加密数据。临时密钥对连接是唯一的,连接完成后双方都会丢弃这些密钥。因此,即使攻击者记录了加密盒,并在之后获取了永久密钥,他也无法打开这些盒(所谓的“完美前向保密”)。
每个“密钥”实际上是一对密钥,一个公钥和一个私钥。数据使用公钥(及其他信息)进行加密,使用私钥进行解密。公钥可以为其他对等方所知(通过某种安全媒介共享),而私钥则绝不会为其他对等方所知。
为了建立连接,客户端必须知道服务器的永久公钥,而服务器可以知道一组客户端永久公钥。在握手过程中,客户端将其永久公钥发送给服务器,由临时密钥保护。服务器因此可以认证客户端,而客户端的永久公钥无法被攻击者从流量中提取。
CurveCP 中的包流如下:
客户端生成一对临时密钥,并将其公钥作为 Hello 包发送给服务器。该包包含一个签名(Box C'->S zeros),用于防止篡改。
-
服务器生成一对新的随机密钥,并将其作为 Cookie 包发送给客户端,其中公钥是明文,私钥在一个盒子中(即“cookie”),只有服务器可以打开,且最多保留两分钟。
-
客户端将 cookie 发送回服务器,并将其永久公钥作为 Initiate 包发送给服务器。此时,服务器可以认证客户端身份并建立连接,打开 cookie 以获取连接的临时密钥。
-
客户端和服务器现在可以发送包含数据的 Message 包,这些数据放在用临时密钥锁定的盒子中。CurveCP 使用固定最大消息大小,旨在适应 UDP 包。
-
我们列出 CurveCP 安全设计旨在防止的具体攻击:
具体防御措施
窃听,即攻击者位于客户端和服务器之间监控数据。我们将所有数据放入只有知道必要私钥的真实接收方才能打开的盒中发送。
-
欺诈性数据,即攻击者创建声称来自服务器或客户端的包。只有知道必要私钥的真实发送方才能创建有效的盒,从而创建有效的包。
-
篡改数据,即攻击者位于客户端和服务器之间以特定方式操纵包。如果包以任何方式被修改,其中包含的盒子将无法打开,因此接收方知道发生了攻击并丢弃该包。
-
重放数据,即攻击者捕获包并在以后重新发送以模仿有效的对等方。我们用唯一的 nonce 加密每个盒子。攻击者无法重放除 Hello 包之外的任何包,而 Hello 包对服务器或客户端没有影响。任何其他重放的包都将被接收方丢弃。
-
放大攻击,即攻击者发出许多小型未认证请求,但使用虚假源地址,以便将大量响应发送给无辜第三方,使其不堪重负。只有 Hello 包是未认证的,并且它们经过填充以大于 Cookie 包。
-
中间人攻击,即攻击者位于客户端和服务器之间替换自己的密钥,以便扮演客户端的“服务器”和服务器的“客户端”,从而能够打开所有盒子。由于永久密钥是事先已知的,攻击者无法成功模仿服务器,如果服务器进行客户端认证,也无法模仿客户端。
-
密钥窃取攻击,即攻击者记录加密数据,然后在稍后阶段获取私钥,可能是通过物理攻击客户端或服务器计算机。客户端和服务器使用临时密钥,并在关闭连接时丢弃。
-
识别客户端,即攻击者通过从包中提取客户端的永久公钥来跟踪客户端的身份。客户端仅在 Initiate 包中发送此信息,并受临时密钥保护。
-
拒绝服务攻击,即攻击者在认证前迫使服务器执行昂贵的运算,从而耗尽服务器资源。CurveCP 使用高速高安全性椭圆曲线密码学,使得典型 CPU 执行公钥运算的速度比典型互联网连接请求这些运算的速度更快。服务器直到客户端发送 Initiate 包后才分配内存。
-
CurveCP 客户端必须知道服务器的永久公钥才能开始连接。服务器可以通过客户端的永久公钥来区分客户端。因此,服务器可以接受未经认证的客户端,但客户端始终会认证服务器。CurveCP 使用永久密钥安全地担保临时密钥,这样每个盒子都可以被认证,但在连接关闭后(且每个对等方丢弃其临时密钥后)无法打开。
认证
CurveCP 不使用客户端 IP 地址作为其安全模型的一部分。首先,因为 IP 地址不安全;其次,这意味着 CurveCP 连接独立于 IP 端点(因此可以在网络变化时迁移)。
CurveCP 不使用客户端 IP 地址作为其安全模型的一部分。首先,因为 IP 地址不安全,其次这意味着 CurveCP 连接独立于 IP 端点(因此可以在网络变化时迁移)。
简洁导向设计
CurveCP(像 NaCl 一样)表达了安全不应该带有选项的观点,因为这会导致糟糕的选择和易受攻击的系统。因此,它没有禁用加密或身份验证的选项。正如 curvecp.org 网站所说,“CurveCP 的服务器身份验证始终处于活动状态且无法禁用。CurveCP 的客户端身份验证始终处于活动状态且无法禁用。CurveCP 的加密始终处于活动状态且无法禁用。CurveCP 的前向保密性始终处于活动状态且无法禁用。CurveCP 没有类似于 IPsec 中 AH 和 ESP 的分离,也没有类似于 HTTPS 重协商的功能。CurveCP 中缺乏选项简化了协议,并防止了各种设计和实现错误。”
使用 CurveCP 的已知问题
要实现或使用 CurveCP,您需要注意其设计中的一些问题和局限性
CurveCP 未尝试防御流量分析攻击。当应用程序发送与真实数据(例如输入的密码、VoIP 呼叫或压缩视频流)对应的报文时,攻击者可以从加密报文的时间、地址和大小中提取有用数据。为了防御流量分析攻击,我们可以用随机数据填充报文,随机发送虚假数据,并模糊心跳 xmessage 和数据 message 之间的区别。
CurveCP 未解释密钥如何交换。在某种程度上,CurveCP 如此简单和健壮是因为它完全忽略了这个问题。然而,任何实际实现都必须解决这个问题。至少,客户端可以配置服务器公钥,并随机生成自己的密钥。这使得客户端可以确保它们正在安全地与正确的服务器通信。然而,服务器如果提前不知道客户端的永久公钥,将无法对客户端进行身份验证。
CurveCP 强制实施服务器到客户端的 1 对多关系。我们可能向 CurveCP 提出的一个改变是在服务器发送的 Message 报文中添加服务器公共临时密钥,以便客户端可以与多个服务器协作(这是 ZeroMQ 应用程序中的一种常见模式)。
CurveCP 依赖于 NaCl。该协议与 NaCl 加密库绑定,该库目前是必要算法的主要实现。NaCl 是一个新兴的库,并且不如其可能的那样广泛可用、稳定或快速。这同样影响 CurveZMQ。
CurveCP 是一个完整的协议栈。这也许是使用 CurveCP 最严峻的挑战:它不仅提供安全性,还在 UDP 上进行流量控制。结果是只有一个实现(在 NaCl 库中),这很难集成到现有系统中。CurveCP 的流量控制效果如何也还不确定。另一方面,TCP 随处可用并且已被充分理解(无论是优点还是缺点)。
CurveZMQ 作为临时解决方案
CurveCP 因多种原因而引人注目,并且很有可能成为一个真正的协议,因为它在一个包中解决了许多人长期以来一直在努力解决的一系列问题。世界需要一个安全、可靠的 UDP 协议,但这需要在内核层面实现,而这需要数年时间。
作为一项临时解决方案,我们仅采用了 CurveCP 的安全握手部分,并在 TCP 之上实现了这一设计,命名为 CurveZMQ。结果令人满意。我们相信验证这个安全模型比验证整个 CurveCP 设计更容易,我们相信这将解决一个眼前需求(在互联网规模上安全消息传递),并且我们相信这将有助于 CurveCP 获得更多关注。
CurveZMQ 参考实现
CurveZMQ 的参考实现遵循本规范,并始终致力于实现最新的稳定版本,是 Curve 项目。