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 握手,包含了两个消息模式:
-> e <- e, ee
pre-message 和普通消息之间使用 "…" 进行分割:
<- 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 使用下面的函数进行回复:
Initialize(handshake_pattern, initiator, prologue, s, e, rs, re)
初始化一个有效的 handshake_pattern 和 initiator 用来指示当前扮演的是发起方还是回复方。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)).
ReadMessage(message, payload_buffer)
从字节流中取出有一个包含了 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))。
对消息中的剩余字节调用 DecryptAndHash()。并将输入储存到 payload_buffer 中。
如果没有剩余消息。调用 Split() 并返回两个新的 ClipherState 对象。