ESCK-7
Авторский алгоритм блочного шифрования.
Автор: Д.Н. Трунов
Общее описание
Особенности алгоритма
- Все операции в шифре выполняются над 64-битными целыми беззнаковыми числами, потому все байты данных и ключа объединяются в группы по 8 байт, образующих 64-битные числа
- Внутренний ключ имеет размер 256 64-битных чисел
- Ключ пользователя или пароль могут иметь любую приемлемую длину (до 2048 байт для байтового ключа или 256 символов для пароля) и преобразуются во внутренний ключ по специальной схеме
- Допустимо преобразовать несколько ключей пользователя и/или паролей в один внутренний ключ
- Данные шифруются блоками по 128 бит (два 64-битных числа)
- Шифрование блока выполняется в несколько проходов (циклов) и их количество не является фиксированным, а может быть задано пользователем
- В шифровании любого блока принимает участие не только ключ, но и 64-битный номер блока, который либо установлен по умолчанию, либо меняется от блока к блоку
Режимы шифрования
Возможные режимы шифрования:
- простой – ключ не меняется и все блоки шифруются одинаково
- счётчик блоков – шифрование с зависимостью от порядкового номера блока
- смена ключа – после шифрования каждого блока внутренний ключ меняется по специальной схеме
Схема шифрования и идея шифра
Порядок шифрования блока:
- Для каждого из двух 64 битных чисел вычисляется специальная 64-битная адресная переменная. При её вычислении принимают участие значение соседнего элемента, номер блока (в простом режиме он всегда равен нулю), а также значение одного из 256 элементов ключа (номер зависит от значения соседнего элемента блока).
- Адресная переменная делится на 8 8-битных номеров, по которым берутся элементы внутреннего ключа для данного этапа шифрования.
- Текущий элемент блока шифруется с участием отобранных элементов ключа с помощью операций сложения (+) исключающего ИЛИ (xor, ^), побитового отрицания (not, ~) и циклических побитовых сдвигов.
- Операции 1-3 выполняются многократно установленное количество раз.
Расшифровка происходит по той же схеме, только чувствительные к порядку операции выполняются в обратной последовательности: если шифруется сначала первый элемент, а потом второй, то расшифровывается сначала второй, а потом первый. Если для шифрования выполняется сначала сложение с K[Num1], потом сдвиг и сложение с K[Num2], то для расшифровки выполняется вычитание K[Num2], обратный сдвиг и вычитание K[Num1].
Идея в том, что на каждом этапе будут вычисляться заранее непредсказуемые значения адресных переменных и, значит, в шифровании элемента блока примут участие разные элементы внутреннего ключа. Зашифрованный элемент повлияет на адресную переменную для соседнего шифруемого, а тот – снова для текущего. От адресной же переменной зависят номера участвующих элементов ключа и потому даже незначительные изменения адреса может существенно повлиять на результат шифрования. При достаточном количестве проходов можно ожидать, что взаимосвязь между соседними элементами блока и всеми элементами ключа будет настолько сложная, что станет равнозначной случайной.
Шифрование блока (функция EncryptBlock) является базовой функцией, которая позволяет зашифровать указанный блок указанным ключом заданное количество раз (циклов) с применением заданного номера блока. Разбивка данных на блоки, возможная смена их номеров, ключа или применение инициализирующего вектора возлагаются на функции шифрования байтовых массивов (Encrypt и EncryptData).
Генерация и смена ключа
Смена (зашифровка) ключа выполняется по специальной схеме, напоминающей схему шифрования блока с некоторыми отличиями:
- шифруются не элементы блока, а сами элементы ключа;
- в вычислении адресной переменной принимают участие оба соседних элемента ключа (следующий и предыдущий), а также сам шифруемый;
- подобная схема шифрования является необратимой и по текущему состоянию внутреннего ключа нельзя восстановить его состояние до зашифровки.
Для генерации внутреннего ключа по ключу пользователя, второй добавляется к первому с помощью операции ^ (xor) и выполняется зашифровка внутреннего ключа с достаточным количеством циклов. Важно привести внутренний ключ к «случайному» виду, когда все элементы отличаются между собой непредсказуемым образом, для чего и применяется зашифровка.
Пример применения: генерация с “солью”, шифрование и расшифровка
// пароли, "соль" и массив данных
std::string Password1;
std::string Password2;
std::vector<uint8_t> Salt(16);
std::vector<uint8_t> Data(1024);
// одноразвый ключ по одному паролю и соли (10000 циклов)
ESCK7 Cipher1;
Cipher1.KeyGenA(Password1, 10000, true); // с инициализацией
Cipher1.KeyGen(Salt, 16, 10000, false); // без инициализации
Cipher1.SetCycles(50); // рабочее количество циклов
Cipher1.SetMode(ESCK7::mdBlCnt); // режим счётчика блоков
// многоразовый ключ по двум паролям (10000 циклов)
ESCK7 Cipher2;
Cipher2.KeyGenA(Password1, 10000, true); // с инициализацией
Cipher2.KeyGenA(Password2, 10000, false);// без инициализации
// одноразовый ключ из многоразового + "соль" (10000 циклов)
ESCK7 Cip = Cipher2;
Cip.KeyGen(Salt, 16, 10000, false); // без инициализации
Cip.SetCycles(50);
Cip.SetMode(ESCK7::mdBlCnt);
// шифрование данных
Cip.Encrypt(Data, Data.size());
// обнуление номера блока и суммы после предыдущего шифрования
Cip.SetBlockNumber(0);
Cip.ResetSum();
// расшифровка данных
Cip.Decrypt(Data, Data.size());
Структура файлов
ESCK7.cpp – основной код шифра, реализованный в виде объектного класса.
ESCK7.h – заголовочный файл для объектного класса.
esck7cip.cpp – пример простой консольной программы для шифрования файлов шифром ESCK-7.
Makefile – файл для сборки программы esck7cip.
Некоторые замечания
Следует отметить, что неотъемлемой частью алгоритма являются только функции шифрования и расшифровки блока (EncryptBlock и DecryptBlock из файла ESCK7.cpp), а также изменения/зашифровки ключа (функция GFunc из того же файла). Остальные функции представляют собой лишь вариант «по умолчанию» для инициализации и генерации ключа или шифрования и расшифровки данных произвольной длины. Для реализации дополнительных вариантов генерации ключа и/или шифрования и расшифровки данных допустимы следующие возможности:
- с помощью функции Key_data() получить указатель на массив ключа конкретного экземпляра класса и генерировать ключ выбранным способом напрямую, минуя запуск KeyGen() (нужно только не забыть установить количество циклов функцией SetCycles);
- реализовать нужный способ генерации ключа и шифрования/расшифровки данных вне класса, но используя его статические функции EncryptBlock(), DecryptBlock() и GFunc();
- реализовать отдельный класс, наследующий ESCK7, и в него добавить необходимые дополнительные функции (ключ K и другие поля класса ESCK7 будут доступны классу-потомку);
- создать отдельный класс, добавить в него базовые статические функции из ESCK7 (в частности, EncryptBlock, DecryptBlock и GFunc), а остальной функционал реализовать в необходимом виде внутри (подобно п. 3) или вне этого класса (подобно п.2);
- вынести базовые функции, в частности EncryptBlock(), DecryptBlock() и GFunc(), в отдельный файл (вне класса) и добавить к ним недостающий функционал генерации ключа и шифрования/расшифровки данных.
Критические изменения алгоритма
24.04.2025. В функциях шифрования ключа (GFunc) и блока (EncryptBlock) прибавление константы заменено прибавлением номера цикла (в DecryptBlock - отниманием).
27.02.2026. Из алгоритма исключена функция EncryptStream() шифрования в потоковом режиме (чтобы не смешивать блочный и потоковый режимы). Потоковый режим реализован в виде отдельного экспериментального класса.
01.04.2026. Из алгоритма полностью исключено применение вектора инициализации. Вместо применения уникального вектора инициализации рекомендуется создавать одноразовые ключи с участием случайной “соли”.