I2P网络的新NTCP2传输协议的实现

I2P传输协议是在大约15年前开发的,当时的主要任务是隐藏流量的内容,而不是使用一种或另一种协议的事实。 当时未考虑DPI(深度数据包检查)和流量阻塞。 但是,时代在变,尽管现有的I2P协议仍然受到很好的保护,但仍需要一种新的传输协议来应对现有和未来的威胁,并且首先需要使用DPI来分析数据包的长度。 此外,新协议使用了最新的加密技术。 该协议的完整描述在这里 。 基础是Noise ,其中将SHA256用作哈希函数,将x25519用作DH(在Noise术语中)。

图片

新密码学


对于NTCP2,除了I2P中已经存在的那些以外,还必须实现以下加密算法:

  • x25519
  • HMACSHA256
  • 茶叉20
  • 聚1305
  • 埃德
  • 西法斯

除Siphash之外,所有这些都在openssl 1.1.0中实现。 反过来,Siphash将出现在openssl 1.1.1中,该版本将很快发布。 为了与大多数当前使用的操作系统中包含的openssl 1.0.2兼容,i2pd添加了由i2pd开发人员Jeff Becker之一(在I2P中称为psi)编写的自己的实现。

与NTCP相比,x25519替代了DH,AEAD / Chaha20 / Poly1305替代了AES-256-CBC / Adler32,而Siphash用于加密传输消息的长度。 计算共享密钥的过程变得更加复杂:通过多次调用HMAC-SHA256。

对RouterInfo的更改


为了使用NTCP2协议,除了两个现有密钥(加密和签名)之外,还引入了第三个密钥x25519,称为静态密钥,该密钥必须在某些RouterInfo地址中作为客户端和服务器的“ s”参数存在。 如果多个地址支持NTCP2(例如ipv4和ipv6),则“ s”在各处都必须相同。 对于客户端,该地址可能仅包含“ s”,而不包含参数“ host”和“ port”。 NTCP2的所需参数也为“ v”,当前始终等于“ 2”。

可以将NTCP2地址设置为带有附加参数的“ NTCP”类型地址-在这种情况下,可以使用NTCP和NTCP2来建立连接,也可以将其设置为仅支持NTCP2连接的NTCP2类型地址。 在Java I2P中,使用第一种方法,在i2pd中使用-第二种。

如果主机接受传入的NTCP2连接,则它必须在建立连接时发布带有值IV的参数“ i”以加密公钥。

建立连接


在建立连接的过程中,各方会生成一对临时密钥x25519,并根据它们和静态密钥来计算用于传输数据的密钥集。 静态密钥也经过身份验证,并与RouterInfo的内容匹配。

双方交换了三个消息:

SessionRequest ------------------->
<-SessionCreated
SessionConfirmed ----------------->

对于每个密钥,计算出一个共同的密钥x25519,称为“输入密钥材料”,然后使用MixKey操作生成消息加密密钥,同时在消息之间保存值ck(链接密钥),并且该值是计算数据传输密钥所依据的结果。 MixKey实现看起来像这样:

MixKey代码
void NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial, uint8_t * derived) { // temp_key = HMAC-SHA256(ck, input_key_material) uint8_t tempKey[32]; unsigned int len; HMAC(EVP_sha256(), m_CK, 32, inputKeyMaterial, 32, tempKey, &len); // ck = HMAC-SHA256(temp_key, byte(0x01)) static uint8_t one[1] = { 1 }; HMAC(EVP_sha256(), tempKey, 32, one, 1, m_CK, &len); // derived = HMAC-SHA256(temp_key, ck || byte(0x02)) m_CK[32] = 2; HMAC(EVP_sha256(), tempKey, 32, m_CK, 33, derived, &len); } 


SessionRequest由客户端的32字节公共密钥x25519和一个加密的AEAD / Chacha20 / Poly1305 16字节数据块+ 16字节哈希以及一个随机数据集(填充)组成,其长度在加密块中传输。 SessionConfirmed消息的后半部分的长度也在那里传输。 基于客户端的临时密钥和服务器的静态密钥,使用密钥对数据块进行加密和签名。 MixKey的初始ck设置为SHA256(“ Noise_XKaesobfse + hs2 + hs3_25519_ChaChaPoly_SHA256”)。

由于dpi可以识别32个字节的公共密钥x25519,因此可以使用AES-256-CBC对其进行加密,其中密钥是服务器地址的哈希值,IV取自RouterInfo中地址的“ i”参数。

SessionCreated的结构与SessionRequest类似,不同之处在于密钥是根据双方的临时密钥计算得出的,并且IV用于从SessionRequest解密/加密公钥后对IV进行加密/解密。

SessionConfirmed由两部分组成:客户端的静态公钥和客户端的RouterInfo。 与以前的消息不同,公共密钥使用与SessionCreated相同的密钥使用AEAD / Chaha20 / Poly1305加密。 因此,第一部分的长度不是32,而是48个字节。 第二部分也使用AEAD / Chaha20 / Poly1305加密,但是使用新密钥,我们根据服务器的临时密钥和客户端的静态密钥进行计算。 同样,可以将随机数据块添加到RouterInfo,但是通常来说,这不是必需的,因为RouterInfo的长度不同。

数据传输的密钥生成


如果在连接设置过程中所有哈希和密钥检查均成功完成,则在两侧的最后一个MixKey之后,都应有相同的ck,从此将在每侧生成2组三组密钥<k,sipk,sipiv>,其中k是AEAD密钥/ Chaha20 / Poly1305,sipk是Siphash的键,sipiv是Siphash的初始IV值,该值在每次应用后都会改变。

密钥生成代码
 void NTCP2Session::KeyDerivationFunctionDataPhase () { uint8_t tempKey[32]; unsigned int len; // temp_key = HMAC-SHA256(ck, zerolen) HMAC(EVP_sha256(), m_Establisher->GetCK (), 32, nullptr, 0, tempKey, &len); static uint8_t one[1] = { 1 }; // k_ab = HMAC-SHA256(temp_key, byte(0x01)). HMAC(EVP_sha256(), tempKey, 32, one, 1, m_Kab, &len); m_Kab[32] = 2; // k_ba = HMAC-SHA256(temp_key, k_ab || byte(0x02)) HMAC(EVP_sha256(), tempKey, 32, m_Kab, 33, m_Kba, &len); static uint8_t ask[4] = { 'a', 's', 'k', 1 }, master[32]; // ask_master = HMAC-SHA256(temp_key, "ask" || byte(0x01)) HMAC(EVP_sha256(), tempKey, 32, ask, 4, master, &len); uint8_t h[39]; memcpy (h, m_Establisher->GetH (), 32); memcpy (h + 32, "siphash", 7); // temp_key = HMAC-SHA256(ask_master, h || "siphash") HMAC(EVP_sha256(), master, 32, h, 39, tempKey, &len); // sip_master = HMAC-SHA256(temp_key, byte(0x01)) HMAC(EVP_sha256(), tempKey, 32, one, 1, master, &len); // temp_key = HMAC-SHA256(sip_master, zerolen) HMAC(EVP_sha256(), master, 32, nullptr, 0, tempKey, &len); // sipkeys_ab = HMAC-SHA256(temp_key, byte(0x01)). HMAC(EVP_sha256(), tempKey, 32, one, 1, m_Sipkeysab, &len); m_Sipkeysab[32] = 2; // sipkeys_ba = HMAC-SHA256(temp_key, sipkeys_ab || byte(0x02)) HMAC(EVP_sha256(), tempKey, 32, m_Sipkeysab, 33, m_Sipkeysba, &len); } 


sipkeys数组的前16个字节是Siphash键,后8个字节是IV。
实际上,Siphash需要两个每个8字节的密钥,但是在i2pd中,它们被视为长度为16字节的1个密钥。

资料传输


数据以帧为单位传输,每个帧由3部分组成:

  1. 通过Siphash加密2字节的帧长
  2. Chacha20加密的数据
  3. 16字节的Poly1305哈希

一帧中传输数据的最大长度为65519字节。

使用XOR操作对当前IV Siphash的前两个字节加密消息长度。

数据由块组成,每个块前面都有一个3字节的标头,其中包含标头类型和长度。 基本上,将发送包含带有已修改头的I2NP消息的I2NP块。 在一个帧中,可以传输几个I2NP块。

块的另一种重要类型是随机数据块,建议将其添加到每个帧中。 它只能是最后一个。

除了它们之外,在当前的NTCP2实现中,还有3种以上的块类型:

  • RouterInfo-通常在建立连接后立即包含RouterInfo服务器,但是可以随时传输任意节点的RouterInfo,以加快泛洪的工作,为此在消息中提供了标志字段。
  • 终止-由节点主动断开连接时由节点发送,指示原因。
  • DateTime-当前时间,以秒为单位。

因此,新的传输协议不仅可以有效抵抗DPI,而且由于采用了更现代,更快速的加密技术,还大大减少了CPU负载,这在处理智能手机和路由器等较弱的设备时尤其重要。 目前,NTCP2支持已在官方I2P和i2pd中完全实现,并将分别在下一版本0.9.36和2.20中正式出现。 要在i2pd中启用ntcp2,请为进入的连接指定配置参数ntcp2.enabled = true,并指定ntcp2.published = true和ntcp2.port = <port>。

Source: https://habr.com/ru/post/zh-CN416785/


All Articles