O módulo de embaralhamento alfabético foi concebido para uma proteção adicional de informações secretas transmitidas através de um canal de comunicação. Embora a encriptação de fluxo RC4 que utilizamos nos Extra Systems Cypher Net não apresente vulnerabilidades na nossa implementação, mantém-se a possibilidade teórica de enumeração de chaves com posterior análise lexical das tentativas de desencriptação da informação transmitida. É claro que não estamos a falar das oportunidades que a humanidade tem hoje. Para os sistemas modernos, o tempo para procurar chaves RC4 no nosso caso é muitas ordens de grandeza superior ao tempo de vida do Universo. Estamos a falar daquelas possibilidades hipotéticas que podem surgir no futuro. Com a intenção de nos protegermos contra quaisquer opções concebíveis, adicionámos ao Extra Systems Cypher Net este módulo de embaralhamento do alfabeto, que torna a análise lexical das tentativas de desencriptação, em princípio, impossível e, portanto, priva um atacante de qualquer oportunidade de quebrar este sistema de comunicação encriptado.
A essência deste método é que os códigos de 256 caracteres (o número de opções para um byte de oito bits) são movidos de forma completamente aleatória na tabela, de modo que o texto em qualquer língua se transforma num jargão completo, consistindo em caracteres impressos e não imprimíveis misturados de uma forma completamente imprevisível. Com isto, o método de cracking por enumeração de força bruta de passwords encriptadas, cuja essência é procurar opções quando o texto cifrado se transforma num conjunto de determinados caracteres impressos, perde completamente a sua validade, pois a password correcta, com a nossa abordagem, aqui descrita, transforma-se, como já foi indicado, em abracadabra, que não pode de forma alguma ser confundida com texto normal.
Assim, o cracker Extra Systems Cypher Net não tem a capacidade de adivinhar a palavra-passe, concentrando-se em obter algum texto significativo.
Inicialmente, o sistema cria uma tabela que corresponde à transformação da identidade (ou seja, não faz nada - não altera a informação de codificação de caracteres). O módulo shuffle_init faz isso:
int main(void) { int i,file_handle; unsigned char data[256]; for (i=0; i<256; i++) data[i] = i; file_handle = creat("shuffle.encrypt", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); write(file_handle, data, sizeof(data)); close(file_handle); return 0; }
De seguida, a informação desta tabela é baralhada aleatoriamente (este procedimento pode, e até deve, ser feito mais do que uma vez). Isto é feito pelo módulo shuffle_randomize:
int main(void) { unsigned char src, dst; unsigned char shuffle_encrypt_data[256]; unsigned char random_src[256], random_dst[256]; int i, file_handle; file_handle = open("shuffle.encrypt", O_RDONLY); read(file_handle, shuffle_encrypt_data, sizeof(shuffle_encrypt_data)); close(file_handle); fill_random_buffer(random_src, sizeof(random_src)); fill_random_buffer(random_dst, sizeof(random_dst)); for(i = 0; i < 256; i++) { src = shuffle_encrypt_data[random_src[i]]; dst = shuffle_encrypt_data[random_dst[i]]; shuffle_encrypt_data[random_src[i]] = dst; shuffle_encrypt_data[random_dst[i]] = src; } file_handle = creat("shuffle.encrypt", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); write(file_handle, shuffle_encrypt_data, sizeof(shuffle_encrypt_data)); close(file_handle); return 0; }
Deve ser especialmente realçado que a função fill_random_buffer (utilizada neste e em vários outros módulos deste sistema) devolve sempre uma sequência criptograficamente forte de bytes aleatórios. Em diferentes sistemas operativos, depende de chamadas a vários serviços de sistema especialmente concebidos e oficialmente documentados (especialmente concebidos e oficialmente declarados como tal, que servem para gerar sequências criptograficamente fortes de bytes aleatórios). Em cada um dos sistemas operativos que suportamos, a função fill_random_buffer, na realidade, apenas encaminha uma chamada para uma função do sistema (e simplesmente devolve os resultados do seu trabalho ao cliente).
A aplicação desta chave no sistema Extra Systems Cypher Net é feita pelo módulo shuffle (note-se que o shuffle_decrypt_data é criado “on the fly” com base no shuffle_encrypt_data imediatamente após ser carregado - este facto garante automaticamente uma total consistência entre os algoritmos de codificação e descodificação da informação transmitida), cujo código em C é o seguinte:
unsigned char shuffle_encrypt_data[256], shuffle_decrypt_data[256]; int load_shuffle_data(void) { unsigned char src; int i, file_handle; if((file_handle = open("shuffle.encrypt", O_RDONLY)) == -1) return 0; read(file_handle, shuffle_encrypt_data, sizeof(shuffle_encrypt_data)); close(file_handle); for(i = 0; i < 256; i++) { src = shuffle_encrypt_data[i]; shuffle_decrypt_data[src] = i; } return 1; } void shuffle_rotate_l(unsigned char *buffer_ptr, int buffer_len) { int i; unsigned char x; if (!buffer_len) return; x = buffer_ptr[0]; for(i = 0; i < (buffer_len - 1); i++) buffer_ptr[i] = (buffer_ptr[i] << 4) | (buffer_ptr[i + 1] >> 4); buffer_ptr[buffer_len - 1] = (buffer_ptr[buffer_len - 1] << 4) | (x >> 4); } void shuffle_rotate_r(unsigned char *buffer_ptr, int buffer_len) { int i; unsigned char x; if (!buffer_len) return; x = buffer_ptr[buffer_len - 1]; for(i = (buffer_len - 1); i > 0; i--) buffer_ptr[i] = (buffer_ptr[i] >> 4) | (buffer_ptr[i - 1] << 4); buffer_ptr[0] = (buffer_ptr[0] >> 4) | (x << 4); } void shuffle_twist(unsigned char *buffer_ptr, int buffer_len) { int i; unsigned char x, y; if (buffer_len < 2) return; for(i = 1; i < buffer_len; i++) { x = buffer_ptr[i - 1]; y = buffer_ptr[i]; buffer_ptr[i - 1] = (x & 0xF0) | (y >> 4); buffer_ptr[i] = (y & 0xF) | (x << 4); } } void shuffle_buffer(unsigned char *buffer_ptr, int buffer_len, unsigned char *shuffle_data) { short counter; for(counter = 0; counter < buffer_len; counter++) buffer_ptr[counter] = shuffle_data[buffer_ptr[counter]]; } void shuffle_swap_l(unsigned char *buffer_ptr, int buffer_len, unsigned char swap_old) { int i; unsigned char x, y, swap_new; if (buffer_len < 2) return; swap_new = ~swap_old; for(i = 1; i < buffer_len; i++) { x = buffer_ptr[i - 1]; y = buffer_ptr[i]; buffer_ptr[i - 1] = (x & swap_old) | (y & swap_new); buffer_ptr[i] = (y & swap_old) | (x & swap_new); } } void shuffle_swap_r(unsigned char *buffer_ptr, int buffer_len, unsigned char swap_old) { int i; unsigned char x, y, swap_new; if (buffer_len < 2) return; swap_new = ~swap_old; for(i = (buffer_len - 1); i > 0; i--) { x = buffer_ptr[i - 1]; y = buffer_ptr[i]; buffer_ptr[i - 1] = (x & swap_old) | (y & swap_new); buffer_ptr[i] = (y & swap_old) | (x & swap_new); } } unsigned char i_shuffle_rol(unsigned char x, unsigned char y) { unsigned char a, b; a = ((x >> 1) & 0xF0) | ((y << 3) & 0x80); b = ((x << 1) & 0x0F) | ((y >> 3) & 0x01); return a | b; } void shuffle_rol(unsigned char *buffer_ptr, int buffer_len) { int i; unsigned char x, y; if (buffer_len < 2) return; for(i = 1; i < buffer_len; i++) { x = buffer_ptr[i - 1]; y = buffer_ptr[i]; buffer_ptr[i - 1] = i_shuffle_rol(x, y); buffer_ptr[i] = i_shuffle_rol(y, x); } } unsigned char i_shuffle_ror(unsigned char x, unsigned char y) { unsigned char a, b; a = ((x & 0xF0) << 1) | ((y >> 3) & 0x10); b = ((x & 0x0F) >> 1) | ((y << 3) & 0x08); return a | b; } void shuffle_ror(unsigned char *buffer_ptr, int buffer_len) { int i; unsigned char x, y; if (buffer_len < 2) return; for(i = (buffer_len - 1); i > 0; i--) { x = buffer_ptr[i - 1]; y = buffer_ptr[i]; buffer_ptr[i - 1] = i_shuffle_ror(x, y); buffer_ptr[i] = i_shuffle_ror(y, x); } } void shuffle_encrypt_buffer(unsigned char *buffer_ptr, int buffer_len) { shuffle_buffer(buffer_ptr, buffer_len, shuffle_encrypt_data); shuffle_rol(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_encrypt_data); shuffle_swap_l(buffer_ptr, buffer_len, SHUFFLE_SWAP_STEP_1); shuffle_buffer(buffer_ptr, buffer_len, shuffle_encrypt_data); shuffle_rotate_l(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_encrypt_data); shuffle_twist(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_encrypt_data); shuffle_rotate_l(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_encrypt_data); shuffle_swap_l(buffer_ptr, buffer_len, SHUFFLE_SWAP_STEP_2); shuffle_buffer(buffer_ptr, buffer_len, shuffle_encrypt_data); shuffle_rol(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_encrypt_data); } void shuffle_decrypt_buffer(unsigned char *buffer_ptr, int buffer_len) { shuffle_buffer(buffer_ptr, buffer_len, shuffle_decrypt_data); shuffle_ror(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_decrypt_data); shuffle_swap_r(buffer_ptr, buffer_len, SHUFFLE_SWAP_STEP_2); shuffle_buffer(buffer_ptr, buffer_len, shuffle_decrypt_data); shuffle_rotate_r(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_decrypt_data); shuffle_twist(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_decrypt_data); shuffle_rotate_r(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_decrypt_data); shuffle_swap_r(buffer_ptr, buffer_len, SHUFFLE_SWAP_STEP_1); shuffle_buffer(buffer_ptr, buffer_len, shuffle_decrypt_data); shuffle_ror(buffer_ptr, buffer_len); shuffle_buffer(buffer_ptr, buffer_len, shuffle_decrypt_data); }
Graças a este rearranjo caótico de letras, o atacante fica completamente privado da capacidade de analisar o texto que obtém ao tentar desencriptar pelo método de força bruta. O ficheiro "shuffle. encrypt" é criado para cada cliente de forma diferente e, além disso, cada cliente, se desejar, está equipado com o programa shuffle_randomize, com o qual pode fazer qualquer número de permutações adicionais neste ficheiro de chaves.
O objetivo dos procedimentos shuffle_rotate_l e shuffle_rotate_r é eliminar as repetições regulares de bytes de chave da codificação UTF8 (que ocorrem quando se utilizam todas as línguas, exceto o inglês - e isto é especialmente típico para o alfabeto cirílico) - uma vez que esta circunstância poderia servir de pista para selecionar a chave RC4 utilizando o método da “força bruta”. A utilização destes procedimentos neste módulo elimina completamente este perigo no nosso produto.
O procedimento shuffle_swap resolve um problema semelhante - a sua função é trocar bits em bytes adjacentes de acordo com uma determinada máscara (SHUFFLE_SWAP_STEP). Selecionámos o tipo específico desta máscara com base no critério de atingir o nível máximo de entropia da sequência final de bytes (previamente encriptada por este módulo). Utilizamos máscaras diferentes para diferentes idiomas. A sua eficiência excede normalmente 7,9 bits de entropia por byte. Não publicamos os valores específicos destas máscaras de bits, uma vez que isso não afeta a força criptográfica do nosso sistema. Esta nossa decisão não viola o princípio de Kerkhoffs, uma vez que os códigos dos algoritmos são aqui publicados na íntegra, e os valores específicos de SHUFFLE_SWAP_STEP não se referem a códigos, mas sim a dados-chave, que o princípio de Kerkhoffs não proíbe ninguém de guardar segredo.
Quanto ao procedimento shuffle_rol (a sua reversibilidade é garantida pelo shuffle_ror), a sua adição à cifra de Pavlenko foi causada pela necessidade de encontrar parâmetros ótimos para aumentar a entropia de uma sequência de bytes abstrata em qualquer língua europeia. A nossa pesquisa preliminar revelou uma série de valores ótimos de SHUFFLE_SWAP_STEP_1 e SHUFFLE_SWAP_STEP_2 que produziram os valores de entropia mais elevados para o texto resultante (sujeito à cifra de Pavlenko), dependendo da língua. Mas os parâmetros que eram ótimos para um idioma não eram ótimos para outro. É para resolver este problema que adicionamos estes procedimentos (shuffle_rol e shuffle_ror) ao nosso algoritmo. Como resultado, a entropia final de qualquer texto em qualquer língua (após processamento com a cifra de Pavlenko) com quaisquer parâmetros óptimos para outra língua excedeu significativamente o valor de 7,9 bits por byte (que é praticamente idêntico a uma sequência puramente aleatória). E, nesta situação, após a adição destes procedimentos, tornou-se claro que o significado prático da escolha de valores específicos do idioma para os parâmetros SHUFFLE_SWAP_STEP_1 e SHUFFLE_SWAP_STEP_2 já não tem qualquer significado prático. Em particular, os valores que selecionámos anteriormente para a língua inglesa funcionam muito bem para todas as outras línguas europeias, incluindo o cirílico (russo e ucraniano).
Quem desejar testar o funcionamento deste algoritmo pode, por exemplo, utilizar os seguintes valores de SHUFFLE_SWAP_STEP_1 e SHUFFLE_SWAP_STEP_2: 0xC3 e 0x3C. Nas nossas experiências, estes parâmetros (puramente aleatórios e de forma alguma os mais ideais) forneceram uma entropia de 7,97 bits por byte para textos em todas as línguas deste site (russo, ucraniano, inglês, etc.) e um desvio padrão inferior a 7 para texto de 8192 bytes de comprimento (a frequência média de ocorrência de bytes neste caso é de 8192:256=32). Isto pode realmente ser chamado de transformação de um texto significativo em qualquer língua europeia numa sequência de símbolos completamente aleatórios.
Naturalmente, qualquer par (ou empresa) de subscritores que comunique através do Extra Systems Cypher Net deve ter um ficheiro “shuffle.encrypt” idêntico. Inicialmente, os subscritores recebem estes ficheiros de nós, mas, como mencionado acima, podem eles próprios criá-los e baralhá-los adicionalmente. É apenas importante que neste caso, para garantir a identidade de todos os conjuntos, os transfiram fisicamente para os seus parceiros (em reunião pessoal ou através de um transportador de confiança).
O conteúdo desta página está também disponível em inglês, francês, alemão, espanhol, italiano, ucraniano e russo.
© Extra Systems, 2024 |
|