четверг, 5 апреля 2018 г.

Алгоритм шифрования ESCK-5: описание, принцип работы, исходные тексты

Трунов Д.Н.
АЛГОРИТМ ШИФРОВАНИЯ ESCK-5: ОПИСАНИЕ, ПРИНЦИП РАБОТЫ, ИСХОДНЫЕ ТЕКСТЫ

Копия статьи, опубликованной 10.11.2017 на сайте https://sites.google.com/site/publicworkstrunov

Введение
Продолжая работу над улучшением алгоритмов шифрования серии ESCK (Encrypting System with Changing Keys – Система шифрования с изменяющимися ключами), рассмотренных в работе [1], была поставлена задача: разработать новый алгоритм с более простой и понятной структурой и, одновременно, с повышенной надёжностью и качеством шифрования. Алгоритмы ESCK-3 и ESCK-4 оказались достаточно громоздкими, с большим количеством потенциальных уязвимостей. Множество циклов шифрования позволяет несколько нивелировать эти уязвимости, однако алгоритм должен быть надёжным не только благодаря большому количеству циклов шифрования.

В качестве ориентира для создания простого и надёжного алгоритма шифрования был выбран так называемый одноразовый блокнот [2] – криптографическая система с доказанной невозможностью вскрытия. В классическом понимании одноразовый блокнот является большой неповторяющейся последовательностью символов ключа, распределённых случайным образом, написанных на кусочках бумаги и приклеенных к листу блокнота. Отправитель использовал каждый символ ключа блокнота для шифрования только одного символа открытого текста. Шифрование представляет собой сложение по модулю 26 (для английского алфавита) символа открытого текста и символа одноразового блокнота.

Каждый символ ключа используется только единожды и для единственного сообщения. Отправитель шифрует сообщение и уничтожает использованные страницы блокнота. Получатель, в свою очередь, используя точно такой же блокнот, дешифрует каждый символ шифротекста. Расшифровав сообщение, получатель уничтожает соответствующие страницы блокнота. Новое сообщение – новые символы ключа.

Абсолютная стойкость такой криптосистемы объясняется отсутствием каких-либо закономерностей в зашифрованных данных. Это свойство достигается при выполнении трёх требований [3]:
 * равенство длин ключа и исходного текста;
 * случайность ключа;
 * однократное использование ключа.

Выполнение всех трёх требований делают такую схему слишком дорогой и непрактичной. Но при невыполнении хотя бы одного из них противнику не составит труда произвести вскрытие такого шифра.

Идея алгоритма ESCK-5
Учитывая существенное неудобство использования одноразового блокнота в чистом виде, требовалось реализовать некоторое приближение к нему при сохранении практичности и удобства использования. Вместо случайного ключа можно использовать ключ, вычисленный генератором псевдослучайных последовательностей [3, 4], в котором важную роль играет источник начальных данных для генерации и качество самого генератора.

В качестве источника данных можно использовать секретный пароль и некоторый объём действительно случайных данных, на основе которых будет генерироваться одноразовый ключ шифрования, не совсем случайный, но трудно предсказуемый без знания пароля. Однако необходимость оперировать дополнительным объёмом данных (сгенерировать, прикрепить к сообщению) создаёт определённые неудобства.

Другим источником данных может быть тот же секретный пароль и само шифруемое сообщение: тогда можно создавать одноразовые и неповторяемые ключи шифрования для разных сообщений без использования дополнительных случайных данных. На этом принципе и построен алгоритм ESCK-5 – новая версия алгоритма симметричного шифрования серии ESCK.

Идея нового алгоритма заключается в том, что каждый элемент сообщения шифруется при помощи одноразового числа-ключа, вычисленного на основе основного ключа и некоторых других элементов сообщения. Сообщение разбивается на блоки одинаковой длины и каждый такой блок шифруется несколько раз подряд (определяется заданным количеством циклов шифрования). Поскольку в процессе шифрования все элементы блока меняются в зависимости от других элементов, в каждом следующем цикле вычисляются совсем другие числа-ключи. После многократного шифрования блока достигается полная зависимость любого элемента блока от всех остальных элементов. Число-ключ будет вычисляться с учётом других элементов, которые уже были изменены тоже с учётом каких-то других элементов и т.д.

Ко всему прочему, по завершении шифрования блока предполагается ещё и изменение основного ключа. Такое изменение возможно даже с участием зашифрованного блока (зависит от режима шифрования). Таким образом, два почти одинаковых сообщения в зашифрованном виде будут отличаться разительно.

Изменение основного ключа, как и его генерация из секретного пароля, производится с помощью некоторой односторонней функции – функции, по результату вычисления которой невозможно (или технически очень сложно) восстановить исходные аргументы. Односторонние функции считаются наиболее обоснованными математически, если речь идёт о шифровании или генераторах псевдослучайных последовательностей [3]. Как всё это реализовано в алгоритме, будет рассмотрено ниже.

Некоторые вопросы криптографии
При разработке предыдущих версий алгоритма ESCK учитывались требования к современным криптографическим системам защиты информации, приведённые в работе [5]. Это общепринятые требования, которые сформулированы следующим образом:
 * зашифрованное сообщение должно поддаваться чтению только при наличии ключа;
 * число операций, необходимых для определения использованного ключа шифрования по фрагменту шифрованного сообщения и соответствующего ему фрагмента открытого текста, должно быть не меньше общего числа возможных ключей;
 * число операций, необходимых для расшифровывания информации путём перебора всевозможных ключей должно иметь строгую нижнюю оценку и выходить за пределы возможностей современных компьютеров (с учетом возможности использования сетевых вычислений);
 * знание алгоритма шифрования не должно влиять на надежность защиты;
 * незначительное изменение ключа должно приводить к существенному изменению вида зашифрованного сообщения даже при использовании одного и того же ключа;
 * структурные элементы алгоритма шифрования должны быть неизменными;
 * дополнительные биты, вводимые в сообщение в процессе шифрования, должны быть полностью и надежно скрыты в шифрованном тексте;
 * длина шифрованного текста должна быть равной длине исходного текста;
 * не должно быть простых и легко устанавливаемых зависимостей между ключами, последовательно используемыми в процессе шифрования;
 * любой ключ из множества возможных должен обеспечивать надежную защиту информации;
 * алгоритм должен допускать как программную, так и аппаратную реализацию, при этом изменение длины ключа не должно вести к качественному ухудшению алгоритма шифрования.

Перечисленные требования дают важные рекомендации по вопросу того, что нужно сделать для создания надёжного алгоритма шифрования, однако они не отвечают на вопрос: как? Если мы говорим, что зашифрованное сообщение должно поддаваться чтению только при наличии ключа, то на вопрос, как это сделать, ответит криптоанализ – наука получения открытого текста, не имея ключа [2]. Успешно проведённый криптоанализ может раскрыть зашифрованный текст или ключ. Он также может обнаружить слабые места в криптосистемах, что в конце концов приведёт к предыдущему результату. Отсюда следует, что надёжный алгоритм шифрования – это алгоритм, устойчивый к криптоанализу (криптографически стойкий).

Следует также понимать, что возможному злоумышленнику (взломщику, противнику) могут быть доступны несколько зашифрованных одним и тем же ключом сообщений, часть открытого сообщения, часть ключа шифрования или несколько пар открытого и зашифрованного сообщения. Ни один из этих случаев не должен дать ощутимых преимуществ взломщику для проведения успешного криптоанализа. Эти факторы учитывались при разработке ESCK-5.

Реализация алгоритма. Объектный класс ESCK5Cypher
Алгоритм создавался в среде разработки Delphi, поэтому все приведённые фрагменты кода написаны на языке программирования Object Pascal [6].

Первый фрагмент, который приведён ниже, – это объявление констант алгоритма и объектного класса ESCK5Cypher. В отличие от ранних версий алгоритма ESCK, реализованных в виде набора функций, ESCK-5 создан в виде объектного класса для удобства и возможности одновременной работы с несколькими шифрами. Константы же преимущественно имеют отношение к режимам шифрования.

Const
    // режимы шифрования
    mdChng = 1;  // смена ключа при переходе между блоками
    mdStr = 2;   // удлинение неполного блока
    mdMin16 = 4; // удлинение до 16 байт, если блок меньше
    mdRndm = 8;  // заполнение свободных ячеек случайными числами
    mdChain = 16;// смена ключа по цепочке

    MaxBufSz = $100000; // 1 MB - размер файлового буфера

Type
    ESCK5Cypher = Class
    Private
        M: Array[0..255] of Cardinal; // рабочий массив
        K: Array[0..255] of Cardinal; // массив ключа
        _Cycles: Cardinal; // количество циклов шифрования
        _Mode: Byte;       // режимы шифрования
        ChSum: Cardinal;   // контрольная сумма
        Buf: Array[0..MaxBufSz-1] of Byte; // файловый буфер
        GenOFF: BOOLEAN;   // режим генерации ключа
        Procedure GFunc;
        Procedure Enc(Size: Byte);
        Procedure Dec(Size: Byte);
    Public
     Procedure Gen(Psw: AnsiString; Cycles: Cardinal;
Mode: Byte);
        Function EncryptMass(Var Mass: Array of Byte;
Size: Cardinal): Cardinal;
        Function DecryptMass(Var Mass: Array of Byte;
Size: Cardinal): Cardinal;
        Function EncryptFile(OriginalFileName: String;
CryptoFileName: String; Password: AnsiString;
Cycles: Cardinal; Mode: Byte): Cardinal;
        Function DecryptFile(CryptoFileName: String;
DecryptoFileName: String; Password: AnsiString): Cardinal;
        Function CheckFileEncryption(CryptoFileName: String;
Password: AnsiString): Cardinal;
        Function GetCheckSum: Cardinal;
        Function GetCycles: Cardinal;
        Function GetMode: Byte;
        Procedure SetGenOFF;
End;

Рассмотрим переменные этого класса (поля по терминологии объектно-ориентированного программирования). Массив M, содержащий 256 чисел типа Cardinal (целые 32-разрядные числа без знака) – это рабочий массив, предназначенный для шифрования/расшифровки отдельного блока данных. Массив K такого же размера и типа предназначен для хранения основного ключа шифрования. В отличие от предыдущих версий, имеющих плавающую длину ключа, здесь длина ключа фиксирована и равна 1 килобайту (256*32 = 8192 бита или 1024 байта).

Поля _Cycles и _Mode предназначены для хранения количества циклов и режима шифрования, ChSum – для вычисления контрольной суммы. Массив Buf представляет собой файловый буфер, который применяется при шифровании и расшифровке файлов. Поле GenOFF предназначено для включения дополнительного режима генерации ключа.

Режимы шифрования
Рассмотрим режимы шифрования, определяемыми битами поля _Mode.

Бит 0: если равен 1 (константа mdChng), то ключ шифрования меняется после вычисления каждого блока, если равен 0, ключ не меняется.

Бит 1: если равен 1 (константа mdStr), то неполный блок удлиняется до полного размера 1 кбайт, если 0 – удлинение не происходит.

Бит 2: если равен 1 (константа mdMin16), то неполный блок удлиняется до длины 16 байт (если он меньше 16, иначе длина не меняется), если 0 – удлинение не происходит. Игнорируется, если бит 1 равен 1.

Бит 3: если равен 1 (константа mdRndm), то при удлинении неполного блока (если такое будет) добавляемые байты заполняются случайными числами, если 0 – добавляемые байты заполняются нулями.

Бит 4: если равен 1 (константа mdChain), то зашифрованный блок участвует в изменении ключа для следующего блока (шифрование по цепочке), если 0 – изменение происходит без участия блока. Игнорируется, если бит 0 равен 0.

Под неполным блоком подразумевается блок данных, размер которого меньше размера массива M. В таком случае массив M либо шифруется, как есть, либо удлиняется до полного размера (или до минимума в 16 байт, в зависимости от выбранного режима). В случае удлинения добавляемые байты после шифрования нужно обязательно хранить вместе с основным массивом (после расшифровки их можно отбросить), что приведёт к увеличению зашифрованных данных по сравнению с исходными.

Следует учесть и тот факт, что массив M состоит из 32-разрядных чисел, а шифруемые данные могут содержаться в байтовом массиве (8-разрядные числа). Байты будут объединяться группами по четыре, но размер байтового массива может не быть кратным четырём. В таком случае к массиву всё равно будет добавлено недостающее число байт (от 1 до 3). В зашифрованном виде их тоже нужно хранить, а при расшифровке – отбросить. В любом случае, необходимо сохранять размер (в байтах) последнего неполного блока, чтобы его можно было восстановить при расшифровке (если последний блок полный, размер неполного равен нулю).

Функция GFunc
Функция GFunc предназначена для изменения ключа шифрования K в процессе первоначальной его генерации или после шифрования блока. Формально это процедура, хотя она тоже возвращает (пусть и не явно) результат – обработанный массив K. Поэтому, дабы не путаться, в дальнейшем будем считать понятия «процедура» и «функция» идентичными.

GFunc является односторонней функцией, меняющей массив K таким образом, что восстановить его первоначальное значение практически невозможно. Ниже приведён текст функции:

Procedure ESCK5Cypher.GFunc;
Var
    I:              Integer;      // счётчик цикла
    A, B, C, D:     Cardinal;     // расчётные величины
    F:              Cardinal;     // вспомогательная величина
    A1, A2, A3, A4: Byte;         // адреса расчётных величин
    Adr:            Cardinal;     // расчётный адрес
    L: Array[0..255] of Cardinal; // массив копии ключа
Begin
    // копирование массива ключа
    For I := 0 to 255 do
        L[I] := K[I];

    // основной цикл шифрования
    For I := 0 to 255 do
    Begin
        // получение текущего и двух следующих элементов массива
        A := K[I];
        Adr := I + 1;
        If Adr > 255 then
        Begin
            Adr := Adr MOD 256;
            B := L[Adr];
        End
        Else
            B := K[Adr];

        Adr := I + 2;
        If Adr > 255 then
        Begin
            Adr := Adr MOD 256;
            C := L[Adr];
        End
        Else
            C := K[Adr];

        // вычисление основного адреса
        Adr := A AND B;
        F := A AND (NOT C);
        Adr := ROL32(Adr XOR F, 7);
        F := (NOT B) AND C;
        Adr := ROL32(Adr XOR F, 7);
        
        // получение 4-х адресов
        A1 := Adr AND $FF;
        A2 := (Adr SHR 8) AND $FF;
        A3 := (Adr SHR 16) AND $FF;
        A4 := (Adr SHR 24);

        // новые расчётные величины
        A := L[A1];
        B := L[A2];
        C := L[A3];
        D := L[A4];

        // расчёт вспомогательной величины
        F := K[I];
        F := ROL32(F XOR (A OR B), 7);
        F := ROL32(F XOR (NOT (A OR C)), 7);
        F := ROL32(F XOR (NOT (A XOR D)), 7);
        F := ROL32(F XOR (B OR (NOT C)), 7);
        F := ROL32(F XOR ((NOT B) OR D), 7);
        F := ROL32(F XOR (NOT (C AND D)), 7);

        // изменение текущего элемента
        K[I] := ROL32(K[I] + A + B + C + D, 5) + F;
    End;
End;

Вычисление начинается с копирования ключа K во временный вспомогательный массив L. Затем происходит основной цикл, в котором каждый элемент изменяется по следующей схеме:
 1. Определяются вспомогательные переменные A = K[I], B = K[I + 1] и C = K[I + 2]. Если I+1 или I+2 выходят за пределы массива K, то значения берутся из начала массива L;
 2. На основе переменных A, B и C вычисляется основной 32-разрядный адрес Adr, который разделяется на четыре 8-разрядные адреса: A1, A2, A3 и A4;
 3. По этим четырём адресам (номерам) из массива L берутся четыре новые вспомогательные переменные: A = L[A1], B = L[A2], C = L[A3], D = L[A4];
 4. На основе новых величин A, B, C, D вычисляется вспомогательная величина F, которая (вместе с величинами A, B, C, D), в конечном итоге, изменяют саму ячейку K[I].

При вычислении величин Adr и F над величинами A, B, C и D выполняются попарно элементарные битовые функции из таблицы 1, а результат накапливается в соответствующей ячейке (Adr или F) при помощи операции XOR (исключающее ИЛИ) и функции ROL32. Поскольку Delphi не поддерживает операции ROL и ROR (циклический сдвиг влево и вправо), для них были созданы две отдельные функции:

// Циклический сдвиг влево
Function ROL32(A, B: Cardinal): Cardinal;
Begin
    B := B AND $1F;
    Result := (A SHL B) OR (A SHR (32-B));
End;

// Циклический сдвиг вправо
Function ROR32(A, B: Cardinal): Cardinal;
Begin
    B := B AND $1F;
    Result := (A SHR B) OR (A SHL (32-B));
End;

Для битовых функций с двумя аргументами существуют всего 16 вариантов таких функций. Если аргументы записать 4-битными числами, причём A = 0011 и B = 0101, то 4-битные результаты с соответствующими им функциями можно представить в виде таблицы 1. Как видно, результаты не всех функций зависят от обоих аргументов. В алгоритме используются только функции, зависимые от обоих аргументов.

Таблица 1 –
Битовые функции для двух аргументов A = 0011 и B = 0101

Для обеспечения необратимости вычисления функцией GFunc применяются данные, взятые из массива L, который уничтожается после вычисления. Играют роль и адреса аргументов, вычисленные по первоначальному значению K[I]. Чтобы попытаться восстановить любое из значений K[I], нужно знать его первоначальное значение и первоначальные значения всех остальных элементов из массива L, но это невозможно.

Применение аргументов по вычисляемым адресам и циклические сдвиги призваны обеспечить техническую невозможность производить вычисления с отдельными битами ключа. Ключ может быть корректно обработан только целиком, как одно большое число. Выполнение элементарных операций, таких как AND, OR, XOR или NOT над разными парами аргументов, призвано устранить возможность поиска более простой функции, возвращающей такой же результат. Наконец, выполнение операции XOR на каждом этапе вычисления Adr или F призвано накапливать результаты вычислений с практически одинаковой вероятностью получения 0 или 1 в битах конечного результата.

Функции Enc и Dec
Функция Enc предназначена для выполнения одного цикла шифрования рабочего массива M. Аргумент Size указывает уменьшенный на единицу полезный размер массива. То есть Size+1 является количеством шифруемых элементов. Ниже приведён текст функции:

Procedure ESCK5Cypher.Enc(Size: Byte);
Var
    I:              Integer;  // счётчик цикла
    A, B, C, D:     Cardinal; // расчётные величины
    F:              Cardinal; // вспомогательная величина
    A1, A2, A3, A4: Byte;     // адреса расчётных величин
    Adr:            Cardinal; // расчётный адрес
Begin
    // основной цикл шифрования
    For I := 0 to Size do
    Begin
        // получение текущего и следующего элементов ключа или массива
        A := K[I];

        Adr := I + 1;
        If Adr > 255 then
            Adr := Adr MOD 256;
    
        B := K[Adr];
        If Adr > Size then
            C := K[Adr]
        Else
            C := M[Adr];

        // вычисление основного адреса
        Adr := NOT (A AND B);
        F := (NOT A) OR C;
        Adr := ROL32(Adr XOR F, 7);
        F := B OR (NOT C);
        Adr := ROL32(Adr XOR F, 7);

        // получение 4-х адресов
        A1 := Adr AND $FF;
        A2 := (Adr SHR 8) AND $FF;
        A3 := (Adr SHR 16) AND $FF;
        A4 := (Adr SHR 24);

        // новые расчётные величины
        If (A1 = I) OR (A1 > Size) then
            A := K[A1]
        Else
            A := M[A1];
    
        If (A2 = I) OR (A2 > Size) then
            B := K[A2]
        Else
            B := M[A2];

        If (A3 = I) OR (A3 > Size) then
            C := K[A3]
        Else
            C := M[A3];

        If (A4 = I) OR (A4 > Size) then
            D := K[A4]
        Else
            D := M[A4];

        // расчёт вспомогательной величины
        If I > 0 then
            F := M[I - 1]
        Else
            F := M[Size];

        If Size = 0 then
            F := 0;

        F := F + K[I];
        F := ROL32(F XOR (NOT (A XOR B)), 7);
        F := ROL32(F XOR (NOT (A OR C)), 7);
        F := ROL32(F XOR (A OR D), 7);
        F := ROL32(F XOR ((NOT B) AND C), 7);
        F := ROL32(F XOR (B AND (NOT D)), 7);
        F := ROL32(F XOR (C AND D), 7);

        // изменение текущего элемента
        M[I] := ROR32(M[I] + F, 5);
    End;
End;

Функция Enc во многом сходна с функцией GFunc, но есть и некоторые различия. Во-первых, зашифровывается массив M, а не K, но аргументы для вычисления величин Adr и F берутся по некоторой схеме как из одного, так и из другого массива. Во-вторых, для вычисления величин Adr и F применяется другой порядок функций из таблицы 1. В-третьих, массив M может шифроваться не целиком, поэтому в качестве параметра функции указывается вычисляемый размер Size. Наконец, поскольку Enc не является односторонней функцией, при шифровании элемента M[I] исключается его попадание в вычисляемые функции в качестве аргумента. Если недопустимо взять какой-то элемент массива M (например, если он находится за пределами вычисляемого размера), то берется элемент массива K с точно таким же номером.

Функция Dec предназначена для выполнения одного цикла расшифровки массива M и выглядит почти полностью, как и функция Enc, с той лишь разницей, что основной цикл выполняется в обратном направлении:

    For I := Size downto 0 do

и изменение текущего элемента работает тоже в обратном порядке:

    M[I] := ROL32(M[I], 5) - F;

Процедура Gen, режим GenOFF
Процедура Gen предназначена для генерации основного ключа шифрования на основе введённого пароля (параметр Psw) с установкой требуемого количества циклов (параметр Cycles) и режима шифрования (параметр Mode). Пароль представлен в виде строки типа AnsiString, в которой каждый символ занимает строго 1 байт. Ниже приведён текст процедуры:

Procedure ESCK5Cypher.Gen(Psw: AnsiString; Cycles: Cardinal; Mode: Byte);
Var
    I: Integer;  // счётчик цикла 1
    L: Integer;  // длина пароля
    J: Cardinal; // счётчик цикла 2
    N: Cardinal; // циклы генерации
Begin
    // установка параметров шифрования
    // установка циклов шифрования
    If Cycles > 0 then
        _Cycles := Cycles
    Else
        _Cycles := 3;

    // установка режима смены ключа
    _Mode := Mode;

    // сброс генерации, если установлен режим GenOFF
    If GenOFF = True then
    Begin
        // отключение режима GenOFF и выход из процедуры
        GenOFF := False;
        Exit;
    End;

    // начальное заполнение (инициализация) массива ключа
    K[0] := $F1D4B79A;
    For I := 1 to 255 do
    Begin
        K[I] := (NOT K[I - 1] XOR (I SHL 15));
        K[I] := ROL32(K[I], 1);
    End;

    // получение длины пароля
    L := Length(Psw);

    // усечение длины на случай превышения 256 символов
    If L > 256 then
        L := 256;

    // посимвольная загрузка пароля и первичная обработка ключа
    For J := 1 to 10 do
    Begin
        For I := 1 to L do
            K[I - 1] := K[I - 1] + Ord(Psw[I]);
        GFunc;
    End;

    // очистка пароля
    Psw := '';

    // определение циклов самозашифровки ключа
    N := Cycles;
    If N < 10 then
        N := 10;

    // самозашифровка ключа
    For J := 1 to N do
        GFunc;
End;

Рассмотрим, как происходит генерация. Вначале копируются значения количества циклов и режима шифрования в соответствующие ячейки (_Cycles и _Mode). Далее, если выключен режим GenOFF, производится начальное заполнение массива ключа стандартными значениями, после которого к каждому элементу K добавляются числовые эквиваленты символов пароля – по одному символу на один элемент K (элементы, выходящие за пределы длины пароля, остаются неизменными). После этого весь ключ K меняется с помощью функции GFunc. Такое добавление с изменением повторяется 10 раз, чтобы исключить даже самую маленькую вероятность того, что два слабо различающихся пароля приведут к генерации одинакового ключа.

После добавления пароля в массив ключа пароль очищается, а массив ключа снова обрабатывается функцией GFunc количеством раз, равным количеству циклов шифрования, но не менее 10 раз.

Режим GenOFF включается процедурой SetGenOFF и позволяет при генерации ключа функцией Gen установить только циклы и режим шифрования, но массив ключа оставить неизменным. Это может быть полезным в некоторых случаях. Например, функция шифрования файла автоматически генерирует ключ на основе указанного пароля. Чтобы использовать вычисленный заранее ключ, можно перед шифрованием файла включить режим GenOFF. Режим действует один раз и затем отключается автоматически. Чтобы использовать его повторно, необходимо снова запустить процедуру SetGenOFF.

Функции EncryptMass и DecryptMass
Функция EncryptMass предназначена для шифрования произвольного байтового массива Mass (первый аргумент) размером Size (второй аргумент). Для шифрования применяется заранее сгенерированный функцией Gen ключ. Ниже приведён текст функции:

Function ESCK5Cypher.EncryptMass(Var Mass: Array of Byte;
Size: Cardinal): Cardinal;
Var
    I:    Integer;  // счётчик цикла
    J:    Integer;  // вспомогательное целое
    C:    Cardinal; // счётчик цикла шифрования
    N:    Cardinal; // циклы самозашифровки ключа
    Pos:  Cardinal; // позиция блока в массиве
    BlSz: Cardinal; // размер блока (байт)
    Sz:   Integer;  // размер блока (количество 32-разр. чисел)
    P:    ^Cardinal;// ссылка на 32-разр. число
Begin
    // проверка создания ключа / установки циклов шифрования
    If _Cycles = 0 then
    Begin
        Result := 1;
    Exit;
    End;

    // проверка кратности массива размеру блока
    If (Length(Mass) MOD 1024 <> 0) then
    Begin
        Result := 2;
        Exit;
    End;

    // заполнение нулями или случайными числами остатка неполного
    // блока (если есть)
    J := 1024 - Size MOD 1024;
    If _Mode AND mdRndm = 0 then
    Begin
        For I := 0 to J - 1 do
            Mass[Size + I] := 0;
    End
    Else
    Begin
        Randomize;
        For I := 0 to J - 1 do
            Mass[Size + I] := Random(256);
    End;

    // увеличение размера массива до кратного 4 (если надо)
    If Size AND 3 <> 0 then
    Size := ((Size SHR 2) + 1) SHL 2;

    Pos := 0;
    ChSum := 0;

    // основной цикл
    While Pos < Size do
    Begin
        // определение байтового размера блока
        BlSz := Size - Pos;
        If BlSz > 1024 then
            BlSz := 1024;

        // определение размера для 32-разр. блока
        If _Mode AND mdStr = 0 then
            Sz := BlSz SHR 2 // BlSz DIV 4
        Else
            Sz := 256;

        // удлинение блока до 16 байт, если он меньше и установлен режим
        If (_Mode AND mdMin16 = mdMin16) AND (Sz < 4) then
            Sz := 4;

        // загрузка текущего блока в рабочий массив
        For I := 0 to Sz - 1 do
        Begin
            P := @Mass[Pos + I SHL 2];
            M[I] := P^;
            ChSum := ChSum + P^;
        End;

        // шифрование блока
        For C := 1 to _Cycles do
            Enc(Sz - 1);

        // возврат блока в основной массив
        For I := 0 to Sz - 1 do
        Begin
            P := @Mass[Pos + I SHL 2];
            P^ := M[I];
        End;

        // смещение позиции на один блок
        Pos := Pos + Sz * 4;

        // к началу цикла если ключ не меняется
        If _Mode AND mdChng = 0 then
            Continue;

        // смена ключа
        // если изменение по цепочке, изменить ключ массивом M
        If _Mode AND mdChain = mdChain then
        Begin
            For I := 0 to Sz - 1 do
            K[I] := K[I] XOR M[I];
        End;

        // определение циклов самозашифровки ключа
        N := _Cycles;
        If N < 10 then
            N := 10;

        // самозашифровка ключа
        For C := 1 to N do
            GFunc;
    End;

    Result := 0;
{
Результаты функции:
0 - нет ошибки
1 - не установлен ключ/циклы шифрования
2 - размер массива не кратен размеру блока/ключа
}
End;

Полный размер массива Mass всегда должен быть кратным 1024 (необходимо на случай возможного удлинения неполного блока), иначе функция завершится с ошибкой. Кроме проверки кратности размера массива, в функции определяется, был ли сгенерирован ключ шифрования. Критерием проверки выступает количество циклов шифрования, которое после генерации ключа не может быть равно 0. Если всё же количество циклов равно нулю, функция тоже завершится, вернув соответствующий код ошибки.

Сам процесс шифрования представляет собой последовательную загрузку частей (блоков) массива Mass в массив M и его многократная зашифровка с помощью процедуры Enc. Процедура Enc запускается столько раз, сколько задано циклов шифрования _Cycles. После завершения шифрования блок возвращается обратно в массив Mass, следом происходит замена ключа (если установлен режим замены) запуском процедуры GFunc количеством раз, равным _Cycles, но не менее 10 раз. Потом берётся следующий блок – и так до конца массива.

Как уже упоминалось, поскольку массив Mass состоит из 8-разрядных чисел, а M – из 32-разрядных, при загрузке в массив 4 байта из Mass помещаются в одну ячейку массива M. Первым идёт младший байт, последним – старший. При выгрузке одно число из M записывается четырьмя байтами в Mass. Если размер массива не кратен 4, то он всё равно будет удлинён на необходимое число байт (от 1 до 3). Напомним, в зашифрованном виде добавленные байты нужно обязательно хранить вместе с основным массивом. При расшифровке лишние байты можно отбросить.

Контрольная сумма ChSum вычисляется простым математическим сложением всех загружаемых в M 32-разрядных чисел. Такой простой способ удобен, если нужно зашифровать несколько отдельных массивов с вычислением общей контрольной суммы. Для этого нужно лишь сложить отдельные контрольные суммы.

Функция DecryptMass предназначена для расшифровки байтового массива Mass и практически идентична рассмотренной выше функции, однако есть некоторые различия. Во-первых, в цикле шифрования блока запускается процедура Dec:

    For C := 1 to _Cycles do
        Dec(Sz - 1);

Во-вторых, контрольная сумма считается не при загрузке, а при выгрузке блока:

    For I := 0 to Sz - 1 do
    Begin
        P := @Mass[Pos + I SHL 2];
        P^ := M[I];
        ChSum := ChSum + P^;
        End;

В-третьих, перед расшифровкой блока выполняется его копирование во вспомогательный
массив L, если блок участвует в смене ключа:

    If (_Mode AND mdChain = mdChain) AND
    (_Mode AND mdChng = mdChng) then
    Begin
        For I := 0 to Sz - 1 do
            L[I] := M[I];
    End;

А изменение ключа (если оно происходит) производится при помощи массива L:

    If _Mode AND mdChain = mdChain then
    Begin
        For I := 0 to Sz - 1 do
            K[I] := K[I] XOR L[I];
    End;

Шифрование файлов. Вспомогательные функции
Для шифрования и расшифровки файлов применяются функции EncryptFile и DecryptFile. В качестве аргументов функции EncryptFile выступают имя оригинального (шифруемого) файла, имя файла, в который будет записан зашифрованный файл, пароль, циклы и режим шифрования. В качестве результата функция возвращает код ошибки (или 0, если ошибок нет). Работа функции заключается в загрузке данных из оригинального файла в файловый буфер, шифрование буфера функцией EncryptMass, сохранение буфера в зашифрованный файл. Если размер оригинального файла превышает размер буфера, то файл загружается и шифруется по частям.

Шифрование функцией EncryptFile также подразумевает, что к зашифрованному файлу будет добавлена некоторая техническая информация, включающая идентификатор алгоритма, количество циклов шифрования, размер последнего неполного блока данных (или 0, если все блоки кратны 1024 байтам), режим шифрования и контрольную сумму. Эти данные нужны для корректной расшифровки файла.

В качестве аргументов функции DecryptFile выступают только имя зашифрованного файла, имя файла для расшифровки и пароль. Остальные параметры шифрования будут определены в самом зашифрованном файле. В обеих рассмотренных функциях предполагается, что файл для записи (второй параметр) будет создан заново, даже если он уже существует. Нельзя первое и второе имена файлов указывать одинаковыми, иначе функция завершится с ошибкой.

Функция CheckFileEncryption тоже выполняет расшифровку указанного файла, но только для проверки шифрования и без сохранения расшифрованных данных. Функции GetCheckSum, GetCycles, GetMode возвращают текущие значения контрольной суммы, циклов шифрования и режима шифрования соответственно.

Пример программы
В качестве примера рассмотрим процедуру ESCK5Test для консольного приложения, приведённую ниже:

Procedure ESCK5Test;
Var
    Cyp: ESCK5Cypher; // экземпляр класса ESCK5Cypher
    R:   Cardinal;    // результат функции
Begin
    // создание нового экземпляра шифра
    Cyp := ESCK5Cypher.Create;

    // шифрование файла и вывод результата
    R:= Cyp.EncryptFile('Original.txt', 'Original.enc', '1234',       10, mdChng OR mdRndm OR mdStr);
    Writeln(R);

    // расшифровка файла и вывод результата
    R := Cyp.DecryptFile('Original.enc', 'Decypher.txt', '1234');
    Writeln(R);

    // освободить Cyp и дождаться нажатия клавиши Enter
    Cyp.Free;
    Readln;
End;

Процедура должна выполнить шифрование текстового файла Original.txt и сохранить зашифрованный файл под именем Original.enc. В качестве параметров принят пароль «1234», 10 циклов шифрования и установлен режим, предполагающий смену ключа (mdChng), удлинение неполного блока (mdStr) и заполнение добавляемых при удлинении байт случайными числами (mdRndm). Затем выполняется расшифровка файла Original.enc с сохранением в файл Decypher.txt. Указан тот же пароль «1234».

Если файл Original.txt существует в каталоге с приложением, то при запуске приложения с выполнением процедуры ESCK5Test, программа выдаст такой результат:
0
0

Это значит, что и шифрование, и расшифровка прошли без ошибок. Если сравнить файлы Original.txt и Decypher.txt (второй появится при выполнении программы), то они окажутся полностью идентичны.

Заключение
В алгоритме шифрования ESCK-5, с одной стороны, удалось реализовать подобие одноразового блокнота на генераторе псевдослучайных чисел, источником данных для которого являются ключ шифрования и само шифруемое сообщение. С другой стороны, полученный алгоритм – это существенно улучшенная и упрощённая версия алгоритма ESCK-4, в которой:
 1) 8-разрядные вычисления заменены 32-разрядными;
 2) установлен фиксированный размер ключа;
 3) существенно улучшены функции шифрования и генерации ключа;
 4) сохранена возможность выбирать требуемое количество циклов шифрования;
 5) добавлены дополнительные режимы шифрования, позволяющие включить или отключить смену ключа или удлинение неполных блоков данных.

Кроме того, в новом алгоритме добавлена возможность шифрования по цепочке, когда предыдущий зашифрованный блок оказывает влияние на ключ шифрования для следующего блока. Эта же возможность позволяет генерировать более сложные ключи, а не только созданные на основе введённого пароля.

Реализация в алгоритме настраиваемых параметров и режимов позволяет получить достаточно широкое пространство для возможных настроек шифрования для самых различных нужд. Благодаря этому существует возможность для каждого конкретного случая подобрать оптимальное соотношение надёжности шифрования с одной стороны, а также удобства и скорости работы – с другой.

Источники информации
1. Трунов Д.Н. Краткое описание алгоритмов шифрования ESCK-3, ESCK-4, P-CRY и DME-1 [Электронный ресурс] – URL: https://sites.google.com/site/publicworkstrunov
2. Шнайер Брюс. Прикладная криптография. Протоколы, алгоритмы, исходные тексты на языке Си. – М.: Триумф, 2002. – 816 с.
3. Абашев А.А, Жуков И.Ю., Иванов М.А., Метлицкий Ю.В., Тетерин И.И. Ассемблер в задачах защиты информации. – М.: КУДИЦ-ОБРАЗ, 2004. – 544 с.
4. Википедия. Генератор псевдослучайных чисел. [Электронный ресурс] URL: https://ru.wikipedia.org/wiki/Генератор_псевдослучайных_чисел
5. Баричев С. Криптография без секретов [Электронный ресурс] // Бюро научно-технической информации. – URL:http://www.bnti.ru/dbtexts/ipks/old/analmat/1_2002/crypto4.pdf (дата обращения: 29.09.2017)
6. Marco Cantu. Object Pascal Handbook. The Complete Guide to the Object Pascal programming language for Delphi developers. – Piacenza (Italy), July 2015

Комментариев нет:

Отправить комментарий