Os protocolos de transporte I2P foram desenvolvidos há quase 15 anos, quando a principal tarefa era ocultar o conteúdo do tráfego, e não o fato de usar um ou outro protocolo. DPI (inspeção profunda de pacotes) e bloqueio de tráfego não foram levados em consideração naquele momento. No entanto, os tempos estão mudando e, embora os protocolos I2P existentes ainda estejam muito bem protegidos, é necessário um novo protocolo de transporte para responder a ameaças existentes e futuras e, primeiro, ao DPI, que analisa os comprimentos dos pacotes. Além disso, o novo protocolo usa os últimos avanços da criptografia. Uma descrição completa do protocolo está
aqui . A base é o
ruído , em que o SHA256 é usado como uma função de hash e x25519 como DH (na terminologia do ruído).
Nova criptografia
Para o NTCP2, além dos já existentes no I2P, é necessário implementar os seguintes algoritmos criptográficos:
- x25519
- HMAC-SHA256
- Chacha20
- Poly1305
- Aead
- Siphash
Todos eles, com exceção do Siphash, são implementados no openssl 1.1.0. Siphash, por sua vez, aparecerá no openssl 1.1.1, que será lançado em breve. Para compatibilidade com o openssl 1.0.2, incluído na maioria dos sistemas operacionais atualmente usados, o i2pd adicionou suas próprias implementações escritas por um dos desenvolvedores do i2pd
Jeff Becker , conhecido como psi no I2P.
Comparado ao NTCP, o x25519 substitui o DH, o AEAD / Chaha20 / Poly1305 substitui o AES-256-CBC / Adler32 e o Siphash é usado para criptografar o tamanho das mensagens transmitidas. O procedimento para calcular a chave compartilhada tornou-se mais complexo: com muitas chamadas para o HMAC-SHA256.
Alterações no RouterInfo
Para trabalhar no protocolo NTCP2, além das duas chaves existentes (criptografia e assinatura), é introduzida uma terceira chave x25519, chamada chave estática, que deve estar presente em algum endereço RouterInfo como parâmetro "s" para clientes e servidores. Se mais de um endereço suportar NTCP2, por exemplo, ipv4 e ipv6, "s" deverá ser o mesmo em todos os lugares. Para clientes, o endereço pode conter apenas "s" e não os parâmetros "host" e "porta". O parâmetro necessário do NTCP2 também é "v", atualmente sempre igual a "2".
O endereço NTCP2 pode ser definido como um endereço do tipo "NTCP" com parâmetros adicionais - nesse caso, a conexão pode ser estabelecida usando NTCP e NTCP2 ou como um endereço do tipo NTCP2 que suporta apenas conexões NTCP2. No Java I2P, o primeiro método é usado, no i2pd - o segundo.
Se o host aceitar conexões NTCP2 de entrada, deverá publicar o parâmetro "i" com o valor IV para criptografar a chave pública ao estabelecer a conexão.
Estabelecer uma conexão
No processo de estabelecimento da conexão, as partes geram pares de chaves temporárias x25519 e, com base nelas e em chaves estáticas, são calculados conjuntos de chaves para transmissão de dados. As chaves estáticas também são autenticadas e combinadas com o conteúdo do RouterInfo.
As partes trocam três mensagens:
SessionRequest ------------------->
<- SessionCreated
SessionConfirmed ----------------->
para cada uma das quais é calculada uma chave comum x25519, chamada "material da chave de entrada", e então uma chave de criptografia de mensagem é gerada usando a operação MixKey, enquanto o valor ck (chave de encadeamento) é salvo entre as mensagens e é o resultado com base no qual as chaves para transmissão de dados são calculadas . A implementação MixKey se parece com isso:
Código MixKeyvoid NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial, uint8_t * derived) {
O SessionRequest consiste em uma chave pública de 32 bytes x25519 do cliente e em um bloco de dados AEAD / Chacha20 / Poly1305 criptografado de 16 bytes + hash de 16 bytes, além de um conjunto de dados aleatórios (preenchimento), cujo comprimento é transmitido no bloco criptografado. O comprimento da segunda metade da mensagem SessionConfirmed também é transmitido lá. O bloco é criptografado e assinado com uma chave baseada na chave temporária do cliente e na chave estática do servidor. O ck inicial para MixKey é definido como SHA256 ("Noise_XKaesobfse + hs2 + hs3_25519_ChaChaPoly_SHA256").
Como 32 bytes da chave pública x25519 podem ser reconhecidos por dpi, eles são criptografados usando o AES-256-CBC, onde a chave é o hash do endereço do servidor e IV é obtido do parâmetro "i" do endereço no RouterInfo.
O SessionCreated na estrutura é semelhante ao SessionRequest, exceto que a chave é calculada com base nas chaves temporárias de ambas as partes, e o IV é usado para criptografia / descriptografia da chave pública IV após a descriptografia / criptografia da chave pública do SessionRequest.
SessionConfirmed consiste em duas partes: a chave pública estática do cliente e o RouterInfo do cliente. Diferentemente das mensagens anteriores, a chave pública é criptografada com AEAD / Chaha20 / Poly1305 com a mesma chave que SessionCreated. Portanto, o comprimento da primeira parte não é 32, mas 48 bytes. A segunda parte também é criptografada com AEAD / Chaha20 / Poly1305, mas com uma nova chave, calculamos com base na chave temporária do servidor e na chave estática do cliente. Além disso, um bloco de dados aleatórios pode ser adicionado ao RouterInfo, mas, como regra, isso não é necessário, porque o comprimento do RouterInfo é diferente.
Geração de chaves para transmissão de dados
Se todas as verificações de hashes e chaves durante a configuração da conexão foram bem-sucedidas, após a última MixKey nos dois lados, deve haver a mesma ck, da qual serão gerados 2 conjuntos de triplos de chaves <k, sipk, sipiv> em cada lado, onde k é a tecla AEAD / Chaha20 / Poly1305, sipk é a chave para Siphash, sipiv é o valor IV inicial para Siphash, que muda após cada aplicativo.
Código Gerador de Chaves void NTCP2Session::KeyDerivationFunctionDataPhase () { uint8_t tempKey[32]; unsigned int len;
Os primeiros 16 bytes da matriz sipkeys são a chave Siphash, os segundos 8 bytes são IV.
Na verdade, o Siphash requer duas chaves de 8 bytes cada, mas no i2pd elas são consideradas como 1 chave com um comprimento de 16 bytes.
Transferência de dados
Os dados são transmitidos em quadros, cada quadro consiste em 3 partes:
- 2 bytes de comprimento de quadro criptografado por Siphash
- dados criptografados por Chacha20
- 16 bytes de hash Poly1305
O comprimento máximo dos dados transmitidos em um quadro é 65519 bytes.
O tamanho da mensagem é criptografado usando a operação XOR com os dois primeiros bytes do atual Siphash IV.
Os dados consistem em blocos, cada bloco é precedido por um cabeçalho de 3 bytes com tipo e comprimento do bloco. Basicamente, os blocos I2NP contendo mensagens I2NP com um cabeçalho modificado são transmitidos. Em um quadro, vários blocos I2NP podem ser transmitidos.
Outro tipo importante de bloco é um bloco de dados aleatórios, recomendado para ser adicionado a cada quadro. Pode ser apenas um e o último.
Além deles, na implementação atual do NTCP2, existem mais 3 tipos de blocos:
- RouterInfo - geralmente contém o servidor RouterInfo imediatamente após o estabelecimento da conexão, mas o RouterInfo de um nó arbitrário pode ser transmitido a qualquer momento para acelerar o trabalho de preenchimentos, para o qual o campo de sinalizador é fornecido na mensagem.
- Rescisão - é enviado pelo nó quando a conexão é interrompida por sua iniciativa, indicando o motivo.
- DateTime - hora atual em segundos.
Assim, o novo protocolo de transporte permite não apenas resistir efetivamente ao DPI, mas também reduz significativamente a carga no processador devido a criptografia e mais moderna e mais rápida, o que é especialmente importante ao trabalhar em dispositivos fracos, como smartphones e roteadores. Atualmente, o suporte ao NTCP2 é totalmente implementado no I2P oficial e no i2pd e aparecerá oficialmente nas próximas versões 0.9.36 e 2.20, respectivamente. Para ativar o ntcp2 no i2pd, especifique o parâmetro de configuração ntcp2.enabled = true e ntcp2.published = true e ntcp2.port = <port> para conexões de entrada.