суббота, 19 октября 2019 г.

Алгоритм криптографической хеш-функции CSA-3


Трунов Д.Н.
АЛГОРИТМ КРИПТОГРАФИЧЕСКОЙ ХЕШ-ФУНКЦИИ CSA-3

Введение
Хеш-функция, или функция свёртки, — функция, осуществляющая преобразование входного массива данных произвольной длины в битовую строку фиксированной длины, выполняемую определённым алгоритмом [1]. Преобразование, производимое хеш-функцией, называется хешированием, исходные данные называются входным массивом, прообразом или сообщением, а результат преобразования — хешем, хеш-кодом или контрольной суммой.
Применяемые в криптографии хеш-функции должны быть криптографически стойкими, то есть быть необратимыми и обладать стойкостью к коллизиям. Необратимая (однонаправленная) хеш-функция — это функция, которая работает только в одном направлении: легко вычислить значение хеш-функции по прообразу, но трудно создать прообраз, значение хеш-функции которого равно заданной величине. Стойкость к коллизиям заключается в том, что трудно создать два прообраза с одинаковым значением хеш-функции.
Безопасность однонаправленной хеш-функции заключается именно в её однонаправленности. У выхода нет видимой зависимости от входа. Изменение одного бита прообраза приводит к изменению, в среднем, половины битов значения хеш-функции. Вычислительно невозможно найти прообраз, соответствующий заданному значению хеш-функции [2].

Идея алгоритмов серии CSA
Требования к хеш-функции определяются спецификой решаемой задачи. Например, при вычислении криптографических контрольных сумм для файлов большого размера имеет значение высокая скорость вычисления. А вот при хешировании паролей в системах аутентификации [3] важна не скорость, а максимальная защита от вскрытия пароля по его хеш-коду (потому в таких системах часто применяется многократное хеширование).
Хеш-функции применяются также и в цифровых подписях [4]. Однако область применения цифровых подписей обычно подразумевает, что вычисленный для подписи хеш-код будет проверяться в пределах ограниченного времени. Надёжность хеш-функции должна быть достаточной, чтобы исключить подделку документа за это ограниченное время. Но как быть, если необходимо исключить подделку документа за более длительный промежуток времени? Как вариант — повышать надёжность хеширования и вычислять одновременно несколько хеш-функций.
В авторских алгоритмах серии CSA были реализованы оба подхода. Идея заключалась в том, чтобы производить вычисления с большим количеством повторений цикла, которое можно менять. Изменение количества повторений цикла позволяет не только регулировать скорость вычисления (функция становится более универсальной), но и вычислять несколько разных хеш-кодов одной функцией (меняя параметры цикла).

Цель разработки алгоритма CSA-3
Предшественником разрабатываемого алгоритма является CSA-2 [5]. В сравнении с распространёнными алгоритмами, например, SHA-2 [6], SHA-3 [7] и ГОСТ Р 34.11-2012 [8,9], CSA-2 обладает следующими недостатками:
* при излишне большом размере блока данных (1024 байта) не обеспечивается должный уровень взаимосвязи между его элементами при малом количестве повторений цикла;
* схема хеширования имеет сильную зависимость от самих хешируемых данных;
* громоздкий двухступенчатый способ образования хеш-кода: вначале код получается размером 1024 байта, затем сжимается до 40 байт, из которых формируется символьная строка результата;
* строка хеша может включать символы (например, «#», «@», «%» и другие), которые некоторыми программами интерпретируются непредсказуемым образом;
* вероятность полного отсутствия изменений результата при изменении одного бита прообраза, предположительно, выше нуля (хотя и очень маленькая);
* элементарная функция обработки каждого отдельного элемента рабочего массива хотя и зависит от нескольких других элементов, однако, в конечном счёте, сводится к простой операции сложения;
* вычисления производятся над 32-битными числами, хотя на современных процессорах эффективнее выполнять 64-битные вычисления.
Разработка алгоритма CSA-3 призвана устранить эти недостатки и при этом сохранить главную идею: повышение надёжности за счёт большого количества повторений цикла и гибкая настройка этого количества.

Класс CSA3Hash. Общие принципы
Алгоритм CSA-3 реализован в виде объектного класса CSA3Hash, объявление которого на языке программирования Object Pascal (Lazarus) приведено ниже (полный текст алгоритма приведён в приложении А):

const
    MaxBufSz = 512*1024; // размер файлового буфера

type
    CSA3Hash = class
    private
        M: Array[0..7] of UInt64; // рабочий массив
        _Cycles: Cardinal; // количество циклов шифрования
        Sum: UInt64; // контрольная сумма
        N: UInt64; // количество обработанных байт
        Passwd: UnicodeString; // пароль (для парольного хеша)
        Buf: Array[0..MaxBufSz-1] of Byte; // файловый буфер
        Stopped: Boolean; // состояние остановки вычислений
    protected
        procedure Init(Cycles: Cardinal);
        procedure Enc;
        procedure EncryptMass(Var Mass: Array of Byte; Size: Cardinal);
        function EncryptFile(FileName: String): Cardinal;
    public
        function GetHash: String;
        function HashMass(Var Mass: Array of Byte; Size: Cardinal;
        Cycles: Cardinal): String;
        function HashString(S: UnicodeString; Cycles: Cardinal): String;
        function HashFile(FileName: String; Cycles: Cardinal): String;
        procedure SetPassword(Password: UnicodeString);
        procedure Stop;
    end;

Хеширование производится функциями HashMass, HashString и HashFile соответственно для байтового массива, текстовой строки в формате Юникод (UTF-16) и файла. Функция GetHash возвращает последний вычисленный хеш-код, SetPassword позволяет установить пароль хеширования, а процедура Stop — остановить вычисления (запускается из другого потока).
Общий принцип хеширования заключается в том, что байтовый массив, строка или файл делятся на блоки по 64 байта и добавляются к рабочему массиву M (предварительно инициализированному процедурой Init), который зашифровывается процедурой Enc. Цикл шифрования выполняется столько раз, сколько задано в поле _Cycles. В шифровании также участвуют поля Sum (контрольная сумма добавляемых данных) и N (общее количество обработанных байт). Из того же массива M впоследствии формируется строка результата.

Процедура EncryptMass
Процедура EncryptMass организует разбивку хешируемого массива на блоки, их добавление в рабочий массив и запуск его шифрования. При добавлении очередного блока в M все его 64 байта разбиваются на группы по 8 байт, которые объединяются в числа типа UInt64 (64-битное целое без знака), а затем присоединяются к соответствующему элементу массива M при помощи операции XOR (попутно добавляясь и к контрольной сумме Sum):

for I := 0 to 7 do
begin
    P := @Mass[Pos + I * 8];
    M[I] := M[I] XOR P^;
    Sum := Sum + P^;
end;

Здесь P — переменная ссылочного типа ^UInt64, а Pos — позиция текущего 64-байтного блока в хешируемом массиве.
Если размер блока J меньше 64, то его содержимое копируется в отдельный массив B, размером 64 байта. Первый свободный (после скопированного блока) байт устанавливается равным $80, а остальные (если есть) заполняются нулями:

for I := 0 to J - 1 do
    B[I] := Mass[Pos + I];

B[J] := $80;

for I := J + 1 to 63 do
    B[I] := 0;

Массив B добавляется в M аналогично добавлению блока из Mass. После добавления каждого блока поле класса N увеличивается на размер блока в байтах (64 для полного блока или значение J для неполного). После этого запускается процедура зашифровки Enc.
Похожим образом работает функция EncryptFile: файл загружается в буфер, разбивается на блоки, которые добавляются к массиву M, а тот шифруется процедурой Enc.

Процедура шифрования Enc
Процедура Enc предназначена для зашифровки рабочего массива M. Перед началом непосредственно шифрования массив M копируется в массив ключа шифрования K. Затем запускается основной цикл шифрования (счётчик цикла J меняется от 1 до _Cycles). Ещё один цикл (счётчик I от 0 до 7) предусматривает изменение каждого элемента массива M[I], причём таким образом, чтобы он зависел от всех элементов ключа K.
Поскольку для каждого элемента массива M берутся одни и те же элементы ключа, массив K предполагается модифицировать перед вычислением M[I]. Причём модификация ключа подразумевает не изменение его бит, а только смену их взаимного расположения.
Первый способ модификации ключа K выполняется в виде циклических смещений (сдвигов) его элементов и зависит от текущего состояния рабочего массива M. Смещение для 8 элементов ключа перед шифрованием каждого из 8 элементов рабочего массива предполагает 64 возможных смещения. Какое из этих 64 смещений будет выполнено, а какое — нет, устанавливается таблицей сдвигов S, представленной в виде целого 64-битного числа. Смещение конкретного элемента на конкретном этапе производится, если соответствующий ему бит в S равен 1, иначе смещение не производится.
Для расчёта S перед циклом изменения массива M рассчитывается временная переменная T, которая зависит от текущего значения счётчика цикла J, значений всех элементов рабочего массива M, а также текущего количества обработанных уже байт N:

T := J;
for I := 0 to 7 do
begin
    Rot := T + M[I];
    T := (Rot SHL 9) OR (Rot SHR 55);
end;

T := T XOR N;

После этого формируется значение S разбивкой полученной переменной T на 8 байт и их заменой по таблице Pi (операция B := T подразумевает получение младшего байта переменной T). В завершение над величиной S и текущим значением контрольной суммы Sum выполняется операция XOR:

S := 0;
for I := 0 to 7 do
begin
    B := T;
    S := (S SHL 8) + Pi[B];
    T := T SHR 8;
end;

S := S XOR Sum;

Когда значение S вычислено, запускается цикл шифрования элементов массива M. В начале этого цикла как раз производятся циклические смещения вправо на 3 бита элементов ключа K (по таблице S):

for L := 0 to 7 do
begin
    if S AND 1 = 1 then
        K[L] := (K[L] SHR 3) OR (K[L] SHL 61);

    S := S SHR 1;
end;

После этого ключ K дополнительно изменяется по одной из двух схем: для чётных номеров I весь массив ключа сдвигается влево на 15 бит как одно большое число, а для нечётных — происходит перестановка элементов K по таблице Tau:

if I AND 1 = 0 then
begin
    T := K[0] SHR 49;
    for L := 0 to 6 do
        K[L] := (K[L] SHL 15) OR (K[L+1] SHR 49);
    K[7] := (K[7] SHL 15) OR T;
end
else
begin
    for L := 0 to 7 do
        K1[L] := K[L];

    for L := 0 to 7 do
        K[L] := K1[Tau[L]];
end;

После этого выполняется непосредственно шифрование элемента M[I]. Вначале в этом участвуют часть элементов ключа K:

Rot := M[I] XOR (K[0] AND K[4]);
M[I] := (Rot SHL 17) OR (Rot SHR 47);
Rot := M[I] XOR (K[1] OR (NOT K[5]));
M[I] := (Rot SHL 19) OR (Rot SHR 45);
Rot := M[I] XOR (NOT (K[2] XOR K[6]));
M[I] := (Rot SHL 13) OR (Rot SHR 51);

Далее к этому прибавляется зашифровка M[I] при помощи соседнего к нему элемента и операции замены по таблице Pi (как в случае с вычислением величины S):

if I = 0 then
    T := M[I] + M[7]
else
    T := M[I] + M[I - 1];
M[I] := 0;
for L := 0 to 7 do
begin
    B := T;
    M[I] := (M[I] SHL 8) + Pi[B];
    T := T SHR 8;
end;

Наконец, завершается зашифровка M[I] при помощи оставшихся незадействованными элементов ключа K:

Rot := M[I] XOR (NOT (K[3] AND K[7]));
M[I] := (Rot SHL 3) OR (Rot SHR 61);

Таблица замен Pi подобрана таким образом, чтобы, вычисляя I := Pi[I] в цикле, можно было обойти всю таблицу. Похожим образом устроена и таблица Tau: перестановки с её помощью в массиве K позволяют произвести 8 перестановок, пока все элементы станут на свои первоначальные места. В процедуре таблицы Pi и Tau объявлены следующим образом:

Pi: Array[0..255] of Byte = (
$20, $19, $CC, $78, $DA, $C4, $24, $A4, $03, $27, $E1, $E5, $2E, $BD, $F2, $08,
$5A, $1E, $7C, $7F, $44, $4A, $46, $DD, $04, $A1, $3F, $58, $F7, $10, $BB, $77,
$37, $88, $EA, $1C, $E9, $47, $BA, $6E, $BF, $E2, $B0, $ED, $82, $E4, $45, $F6,
$3A, $C7, $48, $C9, $A8, $A9, $69, $30, $8A, $26, $14, $21, $A2, $35, $2F, $1B,
$13, $E7, $CA, $01, $71, $90, $18, $7D, $C3, $63, $06, $B5, $56, $F0, $95, $AE,
$B3, $A3, $F9, $5F, $66, $99, $28, $5C, $07, $F5, $2C, $E6, $65, $15, $1A, $85,
$5B, $9D, $34, $0D, $54, $FE, $43, $6B, $DB, $C2, $C6, $38, $BE, $81, $5D, $C0,
$50, $A6, $96, $9A, $2B, $AC, $AA, $0B, $23, $4E, $E8, $4F, $1F, $EB, $C5, $B1,
$FF, $6C, $DC, $CD, $0F, $B7, $79, $D3, $F8, $93, $8E, $22, $EE, $61, $E0, $32,
$17, $BC, $EF, $FD, $83, $9F, $86, $8D, $84, $25, $B4, $33, $52, $9E, $F3, $9B,
$B8, $F1, $D9, $40, $51, $62, $6D, $CB, $59, $94, $B6, $11, $8B, $8C, $F4, $AB,
$02, $CE, $AD, $B2, $68, $60, $0A, $CF, $0E, $00, $A0, $D4, $6F, $53, $D8, $98,
$8F, $B9, $FA, $31, $C1, $89, $49, $3C, $DF, $3D, $7A, $12, $EC, $5E, $36, $91,
$D2, $16, $70, $29, $67, $75, $DE, $3E, $74, $64, $4C, $92, $2D, $1D, $39, $7E,
$A7, $C8, $55, $D6, $2A, $80, $D0, $7B, $87, $AF, $09, $97, $57, $73, $0C, $3B,
$D1, $FB, $41, $4D, $A5, $72, $9C, $FC, $4B, $6A, $42, $05, $76, $D5, $E3, $D7);

Tau: Array[0..7] of Byte = (3, 6, 7, 2, 5, 1, 0, 4);

Процедура начальной инициализации Init
Процедура Init предназначена для установки начальных параметров вычислений, а также инициализации рабочего массива M, который впоследствии будет шифроваться при добавлении в него данных. Основная часть процедуры выполняет сохранение указанного количества повторений цикла шифрования, заполняет массив M, устанавливает равными нулю начальные значения полей Sum и N (контрольная сумма и количество обработанных байт) и завершает процедуру, если пароль хеширования не задан:

if Cycles > 1 then
    _Cycles := Cycles
else
    _Cycles := 2;

M[0] := $F1D4B79A;
M[0] := M[0] SHL 32 + $7D503316;
for I := 1 to 7 do
begin
    M[I] := (NOT M[I - 1] XOR (I SHL 15));
    M[I] := (M[I] SHL 1) OR (M[I] SHR 63);
end;

Sum := 0;
N := 0;

if Passwd = '' then
    Exit;

Если же пароль хеширования задан, выполняется его преобразование в динамический байтовый массив BM, шифрование процедурой EncryptMass (она изменит состояние M), после чего массив освобождается, а пароль очищается:

BM := TEncoding.Unicode.GetBytes(Passwd);
L := Length(BM);
EncryptMass(BM, L);

SetLength(BM, 0);
BM := Nil;

Passwd := '';

Функция GetHash для получения хеш-кода
Функция GetHash преобразует полученный результат шифрования массива M в символьную строку. Для этого массив M разбивается на 64 отдельных байта, каждый из которых делится на 62 и остаток от этого деления формирует символ результата. 62 возможных варианта символа результата составляют следующие группы символов:
* 10 цифр (от 0 до 9);
* 26 символов латиницы верхнего регистра (от A до Z);
* 26 символов латиницы нижнего регистра (от a до z).
В тексте функции это выглядит следующим образом:

Hash := '';

for I := 0 to 7 do
begin
    U := M[I];

    for J := 1 to 8 do
    begin
        Bt := (U MOD 62) + $30;
        U := U SHR 8;
        if Bt > $39 then
            Bt := Bt + 7;
        if Bt > $5A then
            Bt := Bt + 6;

        Hash := Hash + Chr(Bt);
    end;
end;

Result := Hash;

Хеширование байтового массива
Рассмотренных процедур и функций достаточно для того, чтобы вычислять хеши для байтовых массивов или файлов. Для этого нужно лишь инициировать функцию с помощью процедуры Init (указав необходимое количество повторений цикла), запустить процедуру EncryptMass или EncryptFile и получить результат с помощью GetHash. Собственно, так это и реализовано, например, в функции HashMass:

function CSA3Hash.HashMass(Var Mass: Array of Byte; Size: Cardinal;
Cycles: Cardinal): String;
begin
    Init(Cycles);
    EncryptMass(Mass, Size);
    Result := GetHash;
end;

Некоторые замечания
В процедуре EncryptMass загрузка 8 байтов из массива Mass в соответствующую ячейку рабочего массива M производится с помощью ссылочной переменной P. При этом на разных архитектурах эти 8 байт могут объединяться в одно число в разном порядке. Установленный в алгоритме порядок следующий: первым загружается младший байт, последним — старший. То есть при загрузке массива (1, 2, 3, 4, 5, 6, 7, 8) должно получиться число $0807060504030201. Если операция P := @Mass[Pos + I * 8] формирует результат в P^ в другом порядке, её следует заменить на более корректную.
В алгоритме принято, что количество повторений цикла _Cycles не должно быть меньше 2. Если в Init указано меньшее количество, оно обязательно должно быть установлено равным 2.
Если размер хешируемого массива или файла равен нулю, никакие данные в массив M добавляться не будут, как не будет выполняться и его шифрование. Результат хеширования будет получен из начального значения массива M, установленного после процедуры начальной инициализации Init.

Заключение
В отличие от своего предшественника (CSA-2) в данном алгоритме устранены все основные его недостатки:
* блок данных теперь имеет размер 64 байта, при этом обеспечивается высокий уровень взаимосвязи между его элементами;
* зависимость схемы хеширования от исходных данных теперь не настолько сильна, чтобы это ослабляло надёжность хеширования, но достаточна, чтобы обеспечивать тесную взаимосвязь любого бита прообраза с любым битом результата;
* устранён громоздкий двухступенчатый способ вычисления хеш-кода: теперь хеш-код формируется из того же массива, в который добавляются хешируемые данные;
* строка хеша теперь состоит только из символов латиницы и цифр, которые не искажаются другими программами;
* поскольку биты ключа K только переставляются, но не изменяются, это исключает риск нивелирования изменений отдельных битов прообраза, а, значит, исключает и вероятность полного отсутствия изменений результата при изменении одного бита прообраза;
* функция обработки каждого отдельного элемента рабочего массива включает несколько этапов, на каждом из которых этот элемент изменяется;
* вместо 32-разрядных операций теперь выполняются 64-разрядные.

Источники информации
1. Википедия. Хеш-функция. [Электронный ресурс] — URL: https://ru.wikipedia.org/wiki/Хеш-функция
2. Шнайер Брюс. Прикладная криптография. Протоколы, алгоритмы, исходные тексты на языке Си. – М.: Триумф, 2002. – 816 с.
3. Википедия. Аутентификация. [Электронный ресурс] — URL: https://ru.wikipedia.org/wiki/Аутентификация
4. Википедия. Электронная подпись. [Электронный ресурс] — URL: https://ru.wikipedia.org/wiki/Электронная_подпись
5. Трунов Д.Н. Описание и исходный текст алгоритма криптографической подписи CSA-2 [Электронный ресурс] — URL: http://d-raft5.blogspot.com/2018/04/csa-2.html
6. Википедия. SHA-2. [Электронный ресурс] — URL: https://ru.wikipedia.org/wiki/SHA-2
7. Википедия. SHA-3. [Электронный ресурс] — URL: https://ru.wikipedia.org/wiki/SHA-3
8. ГОСТ Р 34.11-2012. Информационная технология. Криптографическая защита информации. Функция хэширования. [Электронный ресурс] — URL: http://docs.cntd.ru/document/gost-r-34-11-2012
9. Википедия. Стрибог (хеш-функция). [Электронный ресурс] — URL: https://ru.wikipedia.org/wiki/Стрибог_(хеш-функция)

Опубликовано 19 октября 2019 г. на https://d-raft5.blogspot.com

© Трунов Д.Н., 2019 г.

Приложение А. Исходный текст алгоритма CSA-3 Object Pascal (Lazarus)
Текст приложения приведён в оригинальном файле.

Приложение Б. Контрольный пример
Текст приложения приведён в оригинальном файле.

Оригинальный файл: Открыть