Implementação do novo protocolo de transporte NTCP2 da rede I2P

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).

imagem

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 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); } 


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; // 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); } 


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:

  1. 2 bytes de comprimento de quadro criptografado por Siphash
  2. dados criptografados por Chacha20
  3. 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.

Source: https://habr.com/ru/post/pt416785/


All Articles