Noise 是一个给予 DH 密钥交换的加密协议框架。

一些有用的资源:

握手状态机

Noise 握手包含了下面几种状态:

  • s, e:本地静态和临时密钥对(可能为空)

  • rs, re:远程静态和临时密钥对(可能为空)

  • h:握手 hash。包含了所有发送和接收的握手数据的 hash。

  • ck:链式密钥。包含了所有先前的 DH 输出。一旦握手完成,链式密钥将会被用于派生用于加密传输信息的密钥。

  • k, n:用于加密的密钥 k(可能为空)和基于计数器的 nonce n。每个新的 DH 输出都会导致重新生成一个新的 ck,同样也会重新生成一个 k。k 和 nonce n 用于加密静态公钥和握手 payload。使用 k 的数据加密使用 AEAD 加密模式,当前 h 值被当作关联数据。在握手阶段,对静态公钥和 payload 进行加密可提供一定的保密性和密钥确认。

一个握手消息由一些 DH 公钥和紧随其后的 payload 构成。payload 可能包含证书或其它由应用包含的数据。要发送一个握手消息,发送方指定 payload 和某个消息模式。消息模式中包含的 token 可能为:

  • e:发送发生成一个新的临时密钥对,并将其储存到变量 e 中、将临时公钥作为明文写到 message buffer 中,然后使用公钥和旧 h 的 hash 作为输入生成新的 h。

  • s:发送方将它的静态公钥作为明文写到 message buffer 中。如果 k 是非空的,则使用 k 进行加密。然后使用旧 h 的 hash 派生新的 h。

  • ee, se, es, ss:通过发起者的密钥对(可能是静态的或临时的,根据第一个字符确定)和接收者的密钥对(根据第二个字符确定)生成一个新的 DH 输出。DH 结果和旧 ck hash 后用来生成新的 ck 和 k,n 被设置为零。

处理完最后一个握手消息后,sender 将 payload 写到 message buffer 中,若 k 不为空则使用 k 进行加密。输出结果和旧 h 一起用来派生一个新的 h。

作为例子,一个简单的、无认证的 DH 握手如下:

-> e
<- e, ee
  • 发起人发送了一个临时公钥。

  • 接收者回复了自己的临时公钥。

然后双方能够计算出一个 DH 密钥。

明文 payload 在第一个消息中发送。位于临时公钥的明文之后。密文 payload 在 response 消息中发送,位于密文临时公钥之后。应用可以发送任意的 payload。

接受者也可以发送它的静态公钥( 密文)并通过一个不同的模式认证它自己:

-> e
<- e, es, s, se

在这种情况下,最终的 ck 和 k 的值是两个 DH 结果的 hash 值。由于 es token 代表了发起者的临时密钥和接受者的静态密钥的 DH 值,因此发起者只要能够成功解密第二个消息,就能够确认接受者的身份。

第二个消息的 payload 可能不包含明文,但是密文中的 payload 中依然包含认证数据。因为加密是 AEAD 模式。

发起者也可以通过发送静态公钥来认证自己:

-> e
<- e, ee, s, se
-> s, se

下面的章节会讲述其中的细节。

握手模式

消息模式是集合 ("e", "s", "ee", "es", "se", "ss", "psk") 中 token 的序列。

一个 pre-message pattern 是下列 tokens 序列之一:

  • "e"

  • "s"

  • "e, s"

  • empty

一个握手模式包含了:

  • 发起方的 pre-message 模式,代表响应方已知的发起方公钥信息。

  • 响应方的 pre-message 模式,代表发起方已知的响应方公钥信息。

  • 一系列实际握手的消息模式。

下面的握手描述了一个无认证的 DH 握手,包含了两个消息模式:

NN
-> e
<- e, ee

pre-message 和普通消息之间使用 "…​" 进行分割:

NK
<- s
...
-> e, es
<- e, ee

HandshakeState 对象

HandshakeState 包含了一个 SymmetricState 和下面的变量。所有的变量都可能为空,为空的变量意味着没有初始化。

变量描述

s

本地静态密钥对

e

本地临时密钥对

rs

远程静态密钥对

re

远程临时密钥对

HandshakeState 还包含了变量用来指示其扮演的角色,以及握手模式的其余部分:

  • initiator:如果为 true,则表示这个 HandshakeState 是发起方。

  • message_patterns:一个消息模式的列表,每个消息模式是一个 token(e, es, ee, es, se, ss, psk) 的列表。

HandshakeState 使用下面的函数进行回复:

  1. Initialize(handshake_pattern, initiator, prologue, s, e, rs, re) 初始化一个有效的 handshake_pattern 和 initiator 用来指示当前扮演的是发起方还是回复方。

  2. WriteMessage(payload, message_buffer) 拿到一个长度可能为零的字节流和一个用来写入的 message_buffer。按下面的步骤进行,如果 EncryptAndHash() 调用失败则返回一个错误:

    • 对于 e: 将 e(必须为空)设置到 GENERATE_KEYPAIR()。将 e.public_key 写入到 buffer。调用 MixHash(e.public_key)

    • 对于 s: 将 EncryptAndHash(s.public_key) 添加到 buffer。

    • 对于 ee: 调用 MixKey(DH(e, re)).

    • 对于 es: 若为发起方调用 MixKey(DH(e, rs));若为响应方调用 MixKey(DH(s, re))。

    • 对于 se: 若为发起方调用 MixKey(DH(s, re));若为响应方调用 MixKey(DH(e, rs))。

    • 对于 ss: 调用 MixKey(DH(s, rs)).

  3. ReadMessage(message, payload_buffer)

  4. 从字节流中取出有一个包含了 Noise 握手信息的消息,和一个用来写明文消息的 payload_buffer。按下面的步骤进行,如果 DecryptAndHash() 调用失败则返回一个错误:

    • 对于 e: Sets re (which must be empty) to the next DHLEN bytes from the message. Calls MixHash(re.public_key).

    • 对于 s: Sets temp to the next DHLEN + 16 bytes of the message if HasKey() == True, or to the next DHLEN bytes otherwise. Sets rs (which must be empty) to DecryptAndHash(temp).

    • 对于 ee: 调用 MixKey(DH(e, re))。

    • 对于 es: 若为发起方调用 MixKey(DH(e, rs));若为响应方调用MixKey(DH(s, re))。

    • 对于 se: 若为发起方调用 MixKey(DH(s, re));若为响应方调用MixKey(DH(e, rs))。

    • 对于 ss: 调用 MixKey(DH(s, rs))。

  5. 对消息中的剩余字节调用 DecryptAndHash()。并将输入储存到 payload_buffer 中。

  6. 如果没有剩余消息。调用 Split() 并返回两个新的 ClipherState 对象。

Last moify: 2025-01-13 05:56:39
Build time:2025-07-18 09:41:42
Powered By asphinx