wireguard使用了ECDH协商算法及noise安全框架故研究了相关知识,针对wireguard握手、数据传输通过源码进行研究。
ECDHE密钥协商算法
DH
| 数学性质 | 已知数据 | 独有数据 |
|---|---|---|
| 离散对数及离散对数的幂运算有交换律 | 指数P、原根a | 指数P、原根a |
离散对数及离散对数的幂运算有交换律 指数P、原根a 各自有自己的离散对数
数学公式:离散对数
如果对于一个整数b和质数p的一个原根a,可以找到一个唯一的指数i,使得:
其中
成立,那么指数i称为b的以a为基数的模p的离散对数。
离散对数难题是指:当已知一个大质数p和它的一个原根a,如果给定一个b,要计算i的值是相当困难的
a为A的私钥 b为B的私钥 a或b为静态
公开 A与B
A:A = G ^ a ( mod P ) B:B = G ^ b ( mod P )
A: B ^ a ( mod P ) = k B: A ^ b ( mod P ) = k
DHE
随机生成临时的私钥a与b
ECDHE
利用了 ECC 椭圆曲线特性,可以用更少的计算量计算出公钥,以及最终的会话密钥
| 数学性质 | 已知数据 | 独有数据 |
|---|---|---|
| 椭圆曲线及椭圆曲线上满足乘法交换和结合律 | 椭圆曲线、椭圆曲线上一基点G | 各自有自己的随机数私钥 |
A: 私钥 d1 公钥 Q1 = d1xG B: 私钥 d2 公钥 Q2 = d2xG
A: d1xQ2 = k B:d2xQ1
Noise安全协议框架
为那些想创建自己的安全协议的开发者提供了一套模板,Noise 协议虽然其初衷是为网络协议提供安全信道,但它并没有规定使用什么样的通讯协议 — TCP / UDP 甚至是任何满足 read / write 接口的子系统,比如文件,管道(pipe),都可以使用 Noise 协议
在握手阶段,发起者和应答者(initiator / responder,注意 Noise 没有使用 Client / Server,但我们可以简单认为 initiator 是 client,而 responder 是 server)通过交换信息对使用何种算法,密钥是什么达成一致。握手阶段双方需要使用同样的协议变量 —— 和 TLS 不同的是,Noise 把协议变量设计为静态而非协商出来的。这是一个很大的简化,而从用户的角度,用户写出来的使用 Noise 的应用往往是自己的节点跟自己的节点通讯,因而无需协商。
WireGuard 使用的是这样的变量:Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s
I:发起者的固定公钥未加密就直接发给应答者
K:应答者的公钥发起者预先就知道
psk2:把预设的密码(Pre-Shared-Key )放在第 2 个握手包之后
ChaChaPoly:对称加密算法
BLAKE2s:哈希算法
协议变量各个部分是这样规定的:Noise <握手的模式> <公钥算法> <对称加密算法> <哈希算法>。其中握手模式有很多种,适用于各种不同的场合。握手的过程中,Noise 会生成一个 HandshakeState,它用来记录当前收到的对端传来的固定公钥和临时公钥,以及通过 ECDH 算法算出来的临时数据。当整个握手结束后,双方都有对等的信息,可以生成一致的密钥。于是发起者和应答者对于它们各自的接收端和发送端生成密钥对。之后,Noise 允许用户将协议状态切换成传输模式(Transport Mode)。切换过程中,HandshakeState 转换成 CipherState,然后就可以用 encrypt / decrypt 来加密和解密应用的数据了。
wireguard中具体细节见 wireguard.pdf
wireguard内核模块源码分析






源码分析
数据结构
wireguard的消息主要分为三种 message_handshake_initiation、message_handshake_response、message_handshake_cookie
发起者 : 发送第一条握手消息
// 消息类型
enum message_type {
MESSAGE_INVALID = 0,
MESSAGE_HANDSHAKE_INITIATION = 1,
MESSAGE_HANDSHAKE_RESPONSE = 2,
MESSAGE_HANDSHAKE_COOKIE = 3,
MESSAGE_DATA = 4
};
message_handshake_initiation = handshake_initiation {
u8 message_type //消息类型
u32 sender_index
u8 unencrypted_ephemeral[32] //发送方为这次握手临时生成的公钥(未加密,用于 ECDH)
u8 encrypted_static[AEAD_LEN(32)] //用对端公钥和临时生成的私钥 ECDH 出的临时密钥 key1 对称加密对方的公钥
u8 encrypted_timestamp[AEAD_LEN(12)] //用对端公钥和自己的私钥 ECDH 出 key2,key2 混淆进 key1,来加密当前的时间戳
u8 mac1[16] //自己的固定公钥加上整个报文内容后的哈希
u8 mac2[16]
}
initiator.chaining_key = HASH(CONSTRUCTION)
initiator.hash = HASH(HASH(initiator.chaining_key || IDENTIFIER) || responder.static_public)
initiator.ephemeral_private = DH_GENERATE()
msg.message_type = 1
msg.reserved_zero = { 0, 0, 0 }
msg.sender_index = little_endian(initiator.sender_index)
msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private)
initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral)
temp = HMAC(initiator.chaining_key, msg.unencrypted_ephemeral)
initiator.chaining_key = HMAC(temp, 0x1)
temp = HMAC(initiator.chaining_key, DH(initiator.ephemeral_private, responder.static_public))
initiator.chaining_key = HMAC(temp, 0x1)
key = HMAC(temp, initiator.chaining_key || 0x2)
msg.encrypted_static = AEAD(key, 0, initiator.static_public, initiator.hash)
initiator.hash = HASH(initiator.hash || msg.encrypted_static)
temp = HMAC(initiator.chaining_key, DH(initiator.static_private, responder.static_public))
initiator.chaining_key = HMAC(temp, 0x1)
key = HMAC(temp, initiator.chaining_key || 0x2)
msg.encrypted_timestamp = AEAD(key, 0, TAI64N(), initiator.hash)
initiator.hash = HASH(initiator.hash || msg.encrypted_timestamp)
msg.mac1 = MAC(HASH(LABEL_MAC1 || responder.static_public), msg[0:offsetof(msg.mac1)])
if (initiator.last_received_cookie is empty or expired)
msg.mac2 = [zeros]
else
msg.mac2 = MAC(initiator.last_received_cookie, msg[0:offsetof(msg.mac2)])接收者:发送第一条握手回复消息
// 消息类型
enum message_type {
MESSAGE_INVALID = 0,
MESSAGE_HANDSHAKE_INITIATION = 1,
MESSAGE_HANDSHAKE_RESPONSE = 2,
MESSAGE_HANDSHAKE_COOKIE = 3,
MESSAGE_DATA = 4
};
message_handshake_initiation = handshake_initiation {
u8 message_type //消息类型
u32 sender_index
u32 receiver_index
u8 unencrypted_ephemeral[32] //接收方为这次握手临时生成的公钥(未加密,用于 ECDH)
u8 encrypted_nothing[AEAD_LEN(0)]
u8 mac1[16] //对端公钥加上整个报文内容后的哈希
u8 mac2[16]
}1.wireguard 维护一张哈希表(key: peer-pubkey value: &peer),如果不做peer移除处理peer数量将会持续增长,超出peer最大限制10^20个。
2.wireguard中对于peer是通过配置文件读取创建的,对peer的处理只能通过用户使用命令进行增删。
3.wireguard中keeplive包只有initiator或者responder设置了peer的persistent keeplive time会发送,目前逻辑中responder peer没有设置persistent keeplive time 故responder端不会发送keeplive包。
4.wireguard中initiator与responder握手成功之后,initiator会主动发一个数据包或者keeplive包给responder,为了交换并检验双方session key协商的结果。
5.wireguard中的session key有9分钟过期时间,创建密session key的时候双方将会启动内核定时器,定时9分钟清空peer对应的session key与临时公私钥。

大佬求带