Los protocolos de transporte I2P se desarrollaron hace casi 15 años, cuando la tarea principal era ocultar el contenido del tráfico, y no el hecho de utilizar uno u otro protocolo. DPI (inspección profunda de paquetes) y el bloqueo de tráfico no se tuvieron en cuenta en ese momento. Sin embargo, los tiempos están cambiando y, aunque los protocolos I2P existentes todavía están bastante bien protegidos, se necesita un nuevo protocolo de transporte para responder a las amenazas existentes y futuras, y, en primer lugar, DPI, que analiza las longitudes de los paquetes. Además, el nuevo protocolo utiliza los últimos avances en criptografía. Una descripción completa del protocolo está
aquí . La base es el
ruido , en el que SHA256 se utiliza como una función hash, y x25519 como DH (en terminología de ruido).
Nueva criptografía
Para NTCP2, además de los que ya existen en I2P, es necesario implementar los siguientes algoritmos criptográficos:
- x25519
- HMAC-SHA256
- Chacha20
- Poly1305
- Aead
- Siphash
Todos ellos, con la excepción de Siphash, se implementan en openssl 1.1.0. Siphash, a su vez, aparecerá en openssl 1.1.1, que se lanzará en breve. Para compatibilidad con openssl 1.0.2, que se incluye en la mayoría de los sistemas operativos actualmente utilizados, i2pd ha agregado sus propias implementaciones escritas por uno de los desarrolladores de i2pd
Jeff Becker , conocido en ps como I ps.
En comparación con NTCP, x25519 reemplaza DH, AEAD / Chaha20 / Poly1305 reemplaza AES-256-CBC / Adler32, y Siphash se usa para cifrar la longitud de los mensajes transmitidos. El procedimiento para calcular la clave compartida se ha vuelto más complejo: con muchas llamadas al HMAC-SHA256.
Cambios a RouterInfo
Para trabajar en el protocolo NTCP2, además de las dos claves existentes (cifrado y firma), se introduce una tercera clave x25519, llamada clave estática, que debe estar presente en alguna dirección de RouterInfo como el parámetro "s" para clientes y servidores. Si más de una dirección admite NTCP2, por ejemplo, ipv4 e ipv6, entonces "s" debe ser la misma en todas partes. Para los clientes, la dirección puede contener solo "s" y no contener los parámetros "host" y "port". También el parámetro requerido de NTCP2 es "v", actualmente siempre igual a "2".
La dirección NTCP2 se puede establecer como una dirección de tipo "NTCP" con parámetros adicionales; en este caso, la conexión se puede establecer utilizando NTCP y NTCP2, o como una dirección de tipo NTCP2 que solo admite conexiones NTCP2. En Java I2P, se usa el primer método, en i2pd, el segundo.
Si el host acepta conexiones NTCP2 entrantes, debe publicar el parámetro "i" con el valor IV para cifrar la clave pública al establecer la conexión.
Establecer una conexión
En el proceso de establecer la conexión, las partes generan pares de claves temporales x25519, y en base a ellas y claves estáticas, se calculan conjuntos de claves para transmitir datos. Las claves estáticas también se autentican y coinciden con el contenido de RouterInfo.
Las partes intercambian tres mensajes:
SessionRequest ------------------->
<- Sesión creada
Sesión confirmada ----------------->
para cada uno de los cuales se calcula una clave común x25519, llamada "material de clave de entrada", y luego se genera una clave de cifrado de mensajes utilizando la operación MixKey, mientras que el valor ck (clave de encadenamiento) se guarda entre mensajes y es el resultado sobre la base de la cual se calculan las claves para la transmisión de datos . La implementación de MixKey se parece a esto:
Código MixKeyvoid NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial, uint8_t * derived) {
SessionRequest consta de una clave pública x25519 de 32 bytes del cliente y un bloque de datos cifrado AEAD / Chacha20 / Poly1305 de 16 bytes + 16 bytes del hash, así como un conjunto de datos aleatorios (relleno), cuya longitud se transmite en el bloque cifrado. La longitud de la segunda mitad del mensaje SessionConfirmed también se transmite allí. El bloque está encriptado y firmado con una clave basada en la clave temporal del cliente y la clave estática del servidor. El ck inicial para MixKey se establece en SHA256 ("Noise_XKaesobfse + hs2 + hs3_25519_ChaChaPoly_SHA256").
Dado que los ppp pueden reconocer 32 bytes de la clave pública x25519, se cifran con AES-256-CBC, donde la clave es el hash de la dirección del servidor, y IV se toma del parámetro "i" de la dirección en RouterInfo.
SessionCreated en estructura es similar a SessionRequest, excepto que la clave se calcula sobre la base de claves temporales de ambas partes, y IV se usa para el cifrado / descifrado de la clave pública IV después del descifrado / cifrado de la clave pública de SessionRequest.
SessionConfirmed consta de dos partes: la clave pública estática del cliente y la RouterInfo del cliente. A diferencia de los mensajes anteriores, la clave pública está cifrada con AEAD / Chaha20 / Poly1305 con la misma clave que SessionCreated. Por lo tanto, la longitud de la primera parte no es 32, sino 48 bytes. La segunda parte también está encriptada con AEAD / Chaha20 / Poly1305, pero con una nueva clave, la calculamos en función de la clave temporal del servidor y la clave estática del cliente. Además, se puede agregar un bloque de datos aleatorios a RouterInfo, pero, como regla, esto no es necesario, porque la longitud de RouterInfo es diferente.
Generación de claves para la transmisión de datos.
Si todas las comprobaciones de hashes y claves durante la configuración de la conexión fueron exitosas, entonces, después de la última MixKey, ambos lados deben tener el mismo ck, a partir del cual se generarán 2 conjuntos de triples de las teclas <k, sipk, sipiv> en cada lado, donde k es la clave AEAD / Chaha20 / Poly1305, sipk es la clave para Siphash, sipiv es el valor inicial de IV para Siphash, que cambia después de cada aplicación.
Código generador clave void NTCP2Session::KeyDerivationFunctionDataPhase () { uint8_t tempKey[32]; unsigned int len;
Los primeros 16 bytes de la matriz sipkeys son la clave Siphash, los segundos 8 bytes son IV.
En realidad, Siphash requiere dos claves de 8 bytes cada una, pero en i2pd se consideran 1 clave con una longitud de 16 bytes.
Transferencia de datos
Los datos se transmiten en cuadros, cada cuadro consta de 3 partes:
- 2 bytes de longitud de trama cifrados por Siphash
- datos encriptados por Chacha20
- 16 bytes de hash Poly1305
La longitud máxima de los datos transmitidos en una trama es de 65519 bytes.
La longitud del mensaje se cifra utilizando la operación XOR con los dos primeros bytes del Siphash IV actual.
Los datos consisten en bloques, cada bloque está precedido por un encabezado de 3 bytes con tipo y longitud de bloque. Básicamente, se transmiten los bloques I2NP que contienen mensajes I2NP con un encabezado modificado. En una trama, se pueden transmitir varios bloques I2NP.
Otro tipo importante de bloque es un bloque de datos aleatorio, que se recomienda agregar a cada cuadro. Puede ser solo uno y el último.
Además de ellos, en la implementación actual de NTCP2 hay 3 tipos de bloques más:
- RouterInfo: generalmente contiene el servidor RouterInfo inmediatamente después de establecer la conexión, pero RouterInfo de un nodo arbitrario se puede transmitir en cualquier momento para acelerar el trabajo de los rellenos, para los cuales se proporciona el campo de marca en el mensaje.
- Terminación: el nodo lo envía cuando la conexión se interrumpe por iniciativa propia, lo que indica el motivo.
- DateTime - hora actual en segundos.
Por lo tanto, el nuevo protocolo de transporte permite no solo resistir efectivamente DPI, sino que también reduce significativamente la carga en el procesador debido a una criptografía más moderna y más rápida, lo cual es especialmente importante cuando se trabaja en dispositivos débiles como teléfonos inteligentes y enrutadores. Actualmente, el soporte de NTCP2 está totalmente implementado en I2P e i2pd oficiales y aparecerá oficialmente en las próximas versiones 0.9.36 y 2.20, respectivamente. Para habilitar ntcp2 en i2pd, especifique el parámetro de configuración ntcp2.enabled = true y ntcp2.published = true y ntcp2.port = <port> para las conexiones entrantes.