The alphabet shuffling module is designed to provide additional protection for secret information transmitted over a communication channel. Although the RC4 stream encryption we use in Extra Systems Cypher Net does not have any vulnerabilities in our implementation, there is still a theoretical possibility of trying keys with subsequent lexical analysis of attempts to decrypt the transmitted information. Of course, we are not talking about the capabilities that humanity has today. For modern systems, the time to try RC4 keys in our case is many orders of magnitude longer than the existence of the Universe. We are talking about those hypothetical capabilities that may appear in the future. In order to insure ourselves against any conceivable options, we added this alphabet shuffling module to Extra Systems Cypher Net, which makes lexical analysis of decryption attempts impossible in principle, and thus deprives an attacker of any opportunity to hack this encrypted communication system. The essence of this method is that the codes of 256 symbols (the number of variants for one byte of eight bits) are moved around in the table in a completely random way, so that the text in any language turns into complete gibberish, consisting of printed and non-printable symbols, mixed together in a completely unpredictable way. As a result, the method of hacking by brute-force encryption passwords, the essence of which is to search for variants when the ciphertext turns into a set of some printed symbols, completely loses its validity, since the correct password, with our approach, which is described here, turns, as already indicated, into gibberish, which can in no way be taken for normal text.
Thus, the Extra Systems Cypher Net hacker does not have the ability to select a password, focusing on getting some meaningful text.
Initially, a table is created in the system that corresponds to the identical transformation (that is, it does nothing - does not change the information about the character encoding). This is done by the shuffle_init module:
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; }
The information in this table is then randomly shuffled (this procedure can, and even should, be done more than once). This is done by the shuffle_randomize module:
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; }
It should be especially emphasized that the fill_random_buffer function (used in this and a number of other modules of this system) always returns a cryptographically strong sequence of random bytes. In different operating systems, it relies on calls to various specially designed and officially documented system services (specially designed and officially declared as such, which serve to generate cryptographically strong sequences of random bytes). In each of the operating systems we support, the fill_random_buffer function actually only redirects the call to such a system function (and simply returns the results of its work to its client).
The application of this key in the Extra Systems Cypher Net system is handled by the shuffle module (note that shuffle_decrypt_data is created "on the fly" based on shuffle_encrypt_data immediately after it is loaded - this fact automatically ensures complete consistency of the encoding and decoding algorithms for the transmitted information), the code of which in C looks like this:
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); }
Thanks to this chaotic permutation of letters, the attacker is completely deprived of the ability to analyze the text that he gets when trying to decrypt it using the brute force method. The "shuffle.encrypt" file is generated for each client individually, and in addition, each client, if desired, is supplied with the shuffle_randomize program, with the help of which he himself can make any number of additional permutations in this key file.
The purpose of the shuffle_rotate_l and shuffle_rotate_r procedures is to eliminate regular repetitions of key bytes of the UTF8 encoding (which occur when using all languages except English - and this is especially typical for the Cyrillic alphabet) - since this circumstance could serve as a hook for selecting the RC4 key by the "brute force" method. Our use of these procedures in this module completely eliminates this danger in our product.
A similar task is solved by the shuffle_swap procedure — its work consists of swapping bits in adjacent bytes according to a certain mask (SHUFFLE_SWAP_STEP). We select the specific type of this mask based on the criterion of achieving the maximum entropy level of the final byte sequence (previously encrypted by this module). We use different masks for different languages. Their efficiency, as a rule, exceeds the indicator of 7.9 bits of entropy per byte. We do not publish the specific values of these bit masks, since this does not affect the cryptographic strength of our system in any way. This solution of ours does not violate the Kerckhoffs principle, since we have fully published the codes of the algorithms here, and the specific values of SHUFFLE_SWAP_STEP do not relate to codes, but rather to key data, which the Kerckhoffs principle does not prohibit anyone from keeping secret.
As for the shuffle_rol procedure (its reversibility is ensured by shuffle_ror), its addition to the Pavlenko cipher was caused by the need to find optimal parameters for increasing the entropy of an abstract byte sequence in any European language. Our preliminary studies revealed a number of optimal values of SHUFFLE_SWAP_STEP_1 and SHUFFLE_SWAP_STEP_2, which created maximum entropy values for the final text (subjected to the Pavlenko cipher) depending on the language. However, the optimal parameters for one language were not optimal for another. It was precisely to solve this problem that we added these procedures (shuffle_rol and shuffle_ror) to our algorithm. As a result, the final entropy of any text in any language (after processing by the Pavlenko cipher) with any parameters optimal for another language significantly exceeded the figure of 7.9 bits per byte (which is almost identical to a purely random sequence). And, in this situation, after adding these procedures, it became clear that the practical significance of choosing language-specific values for the SHUFFLE_SWAP_STEP_1 and SHUFFLE_SWAP_STEP_2 parameters no longer has any practical significance. In particular, the values we selected earlier for English work great for all other European languages, including Cyrillic (Russian and Ukrainian).
Those who wish to test the operation of this algorithm can, for example, use such values of SHUFFLE_SWAP_STEP_1 and SHUFFLE_SWAP_STEP_2 as 0xC3 and 0x3C. In our experiments, these (purely random, and by no means the most optimal) parameters provided an entropy of 7.97 bits per byte for texts in all languages of this site (Russian, Ukrainian, English, etc.) and a standard deviation of less than 7 for a text of 8192 bytes (the average frequency of occurrence of bytes in this case is 8192:256=32). This can truly be called the transformation of a meaningful text in any European language into a sequence of absolutely random symbols.
Naturally, any pair (or company) of subscribers communicating with each other via Extra Systems Cypher Net must have an identical "shuffle.encrypt" file. Initially, subscribers receive these files from us, but, as noted above, they can also create them themselves and additionally shuffle them. It is only important that in this case, to ensure the identity of all sets, they physically transfer them to their partners (at a personal meeting or through a trusted courier).
The content of this page is also available in French, German, Portuguese, Spanish, Italian, Ukrainian and Russian.
© Extra Systems, 2024 |
|