четверг, 26 декабря 2019 г.

PasswordKeeper4. Особенности реализации новой версии программы для хранения паролей


Трунов Д.Н.
PASSWORDKEEPER 4. ОСОБЕННОСТИ РЕАЛИЗАЦИИ НОВОЙ ВЕРСИИ ПРОГРАММЫ ДЛЯ ХРАНЕНИЯ ПАРОЛЕЙ

Введение
С одной стороны, использование любой специализированной программы для хранения паролей — уже лучше хранения паролей в открытом виде в текстовом файле или электронной таблице. Такой файл может быть с лёгкостью открыт другим пользователем компьютера или попасть в чужие руки с утерянной флешкой или из-за утечки в облачном хранилище [1]. С другой стороны, использование программы для хранения паролей как бы говорит всем: пароли здесь. Чем популярнее программа, тем сильнее интерес к ней злоумышленников. Нельзя исключать и того, что недобросовестные разработчики могут целенаправленно включить в свою программу лазейки для кражи паролей пользователей.
Выбирая решение для хранения паролей можно исходить из доверия к компании-разработчику и/или опыту сообщества пользователей. Как правило, крупные компании дорожат своей репутацией, чтобы выпускать заведомо небезопасный продукт. Польза от кражи пользовательских паролей обычно явно ниже, чем ущерб от потери доверия клиентов и падения продаж. Что касается программ с открытым исходным кодом, то в отсутствии в них уязвимости и лазеек может убедиться каждый, изучив текст программы.
Но в любом правиле могут быть исключения. Репутация разработчика или открытый исходный код не являются стопроцентной гарантией надёжности, поскольку пользователь не может контролировать процесс создания программы и выхода для неё обновлений (точно ли очередное обновление призвано улучшить защиту?). Поэтому ещё одним вариантом является выбор в пользу контроля над созданием программы. Это либо написание её самостоятельно, либо собственноручная сборка из исходных текстов (после тщательной их проверки).

Идея программы PasswordKeeper
Идея написания собственной программы для хранения паролей, прежде всего, преследует цель полного контроля над разработкой с гарантией отсутствия заведомых лазеек в защите. Реализация лишь наиболее необходимых функций упрощает программу и облегчает её анализ на предмет наличия ошибок и уязвимостей. При этом есть возможность реализовать полезные функции, отсутствующие в других решениях.
Надёжная программа для хранения паролей должна применять стойкое шифрование, чтобы файл с паролями, попади он в руки злоумышленнику, не был открыт без знания секретного ключа. Также работающая программа должна иметь защиту против попыток вредоносных программ, запущенных в той же системе, получить доступ к сохранённым паролям или ключу. И, как уже упоминалось, не иметь заведомо оставленных лазеек.
Если возложить функции защиты от возможных вредоносных действий других программ на операционную систему (антивирус, фаервол), то задача может несколько упроститься: достаточно будет реализовать стойкое шифрование и обеспечить отсутствие лазеек. Остальную защиту выполнять средствами операционной системы и специализированных программ.

Требования к программе PasswordKeeper 4
Основной целью создания новой (4-й) версии программы PasswordKeeper является доработка и улучшение функций предыдущей (3-й) версии программы [2], с учётом анализа преимуществ и недостатков существующих программ для хранения паролей, в частности KeePassXC [3]. При этом к программе предъявляются следующие требования:
1) она должна быть портативной и выполняться без установки и дополнительной настройки на операционных системах Windows и Linux;
2) в отличие от предыдущей версии, парольные записи в новой программе не должны ограничиваться только заголовком и паролем с заметкой: нужны дополнительные поля для хранения названий сайтов или программ, а также логинов;
3) при изменении сохранённого пароля предыдущий должен сохраняться в истории. Нужны функции работы с историей паролей: копирование паролей из истории или их удаление;
4) функцию изменения порядка парольных записей необходимо исключить, а вместо неё реализовать отображение записей в алфавитном порядке;
5) для удобства работы необходима возможность разделять записи по папкам или категориям;
6) как и в KeePassXC, необходима возможность работать с файлами-ключами;
7) необходима возможность генерировать файл-ключ по заданному паролю (чего нет в KeePassXC).

Основная реализация программы
Для реализации кроссплатформенности была выбрана среда разработки Lazarus [4], позволяющая создавать программы и для Windows, и для Linux, причём с минимальными изменениями (или вообще без них) в исходном коде.
Для организации работы с записями было решено применить такую динамическую структуру данных, как упорядоченный список [5]. Для этого в программе объявляется два типа данных: запись NoteR и ссылка на неё TNote. Переменная FirstNote типа TNote ссылается на первую запись или содержит Nil (если записей нет). Ниже приведено объявление типов NoteR и TNote:

type
  NoteR = Record
    Title: String;  // заголовок записи
    Site:  String;  // название сайта/приложения
    Login: String;  // имя пользователя/логин
    Categ: String;  // категория записи (ярлык)
    Prot:  Array of Byte; // защищённая шифрованием часть
    Len:   Word;    // размер защищённых данных
    Next:  Pointer; // ссылка на следующую запись
  end;
  TNote = ^NoteR;


Каждая запись содержит заголовок Title, название сайта Site, имя пользователя Login, название категории Categ, массив защищённой части Prot, размер зашифрованных данных Len и указатель на следующую запись в списке Next (для последней записи в списке Next = Nil). Защищённая часть Prot хранит в зашифрованном виде пароль, историю паролей и заметку.
При добавлении записи создаётся новая динамическая переменная типа TNote, и в ней устанавливаются значения полей. Добавляемый пароль, история (в новой записи она пустая) и заметка объединяются в одну строку через специальные символы-разделители. Затем эта строка преобразуется в байтовый массив соответствующей длины и зашифровывается блочным шифром. Полученный массив (точнее, ссылка на него) помещается в поле Prot, а в поле Len сохраняется длина объединённой строки (размер зашифрованного массива может быть больше изначальной длины строки, потому Len будет нужна для корректной расшифровки).
Добавление полученной записи в список осуществляется обычным для списков способом: определяется позиция записи в списке, указатель Next предыдущей записи копируется в указатель добавляемой записи, а в указатель предыдущей помещается ссылка на текущую запись. После добавления предыдущая запись будет указывать на текущую, а текущая — на следующую (на которую ранее ссылалась предыдущая). Если добавляется самая первая запись, то ссылка на неё помещается в FirstNote, а поле Next записи должно содержать Nil (пустой указатель).
Позиция добавляемой записи в списке определяется исходя из условия соблюдения упорядоченности списка по заголовкам Title. При поиске позиции для добавления проверяются заголовки всех записей, пока не будет найдена подходящая позиция или пока не завершится список. Во втором случае новая запись будет добавлена в конец списка.
При удалении записи значение указателя Next предыдущей записи меняется на значение указателя текущей и, таким образом, удаляемая запись исключается из списка. После этого освобождается массив защищённой части Prot, а затем — вся удаляемая запись.
Изменение существующей записи осуществляется следующим путём. Запись открывается с расшифровкой и извлечением защищённой части: пароль+история+заметка. История паролей сохраняется и к ней добавляется текущий пароль (если он изменился, иначе история остаётся неизменной), после чего запись удаляется и добавляется заново в изменённом виде вместе с существующей историей. Историю паролей тоже можно менять, удаляя из неё либо отдельные пароли, либо все вместе.
После любой операции добавления, изменения или удаления записи весь список сохраняется в ранее созданный для этого или открытый файл-хранилище.

Реализация шифрования и сохранения/загрузки хранилища записей
Схема шифрования в PasswordKeeper 4 реализована с участием трёх алгоритмов авторской разработки: блочный шифр ESCK-6[6], потоковый шифр ШОРТ (Short)[7] и алгоритм хеш-функции CSA-3[8]. При каждом создании нового хранилища или при открытии существующего создаётся экземпляр блочного шифра Cyp на основе введённого мастер-пароля Password с количеством циклов (количеством повторений главного цикла шифрования) Cycles. Этот шифр потом необходим для шифрования/расшифровки защищённых массивов Prot записей.
Кроме шифра Cyp, вычисляется также строка Psw. Для её вычисления сначала создаётся шаблонный массив Mass, «расшифровывается» шифром Cyp, а затем хешируется по алгоритму CSA-3 с количеством циклов, равным 10000. В строке Psw сохраняется результат хеширования. Ниже приведён фрагмент текста программы:

// создать блочный шифр и генерировать ключ
Cyp := ESCK6Cypher.Create;
Cyp.Gen(Password, Cycles, mdRndm);
Password := '';

// создать шаблонный массив и "расшифровать" полученным ключом
for I := 0 to 2047 do
Mass[I] := I;
Cyp.DecryptMass(Mass, 2048);

// хешировать полученный массив и освободить созданный экземпляр H
H := CSA3Hash.Create;
Psw := H.HashMass(Mass, 2048, 10000);
H.Free;

«Расшифровка» шаблонного массива нужна, чтобы преобразовать его с помощью полученного ключа Cyp, который, в свою очередь, зависит от мастер-пароля Password. Можно было применить и зашифровку — суть от этого сильно не меняется. Только при другой длине массива это могло бы породить проблему: из-за возможного добавления случайных данных в конец неполного блока результат зашифровки может быть каждый раз разный, тогда разными будут и результаты вычисления Psw.
Строка Psw нужна для создания потокового шифра, которым шифруются или расшифровываются записи при каждом сохранении в файл или чтении из него. При сохранении списка записей вначале генерируется «соль» — строка Salt, содержащая случайные символы. Она сохраняется в файл для возможности последующей расшифровки. Соответственно, при чтении списка записей строка Salt будет просто прочитана из файла.
Затем строка Salt хешируется по алгоритму CSA-3 с ранее полученной строкой Psw в качестве пароля хеша. Полученный хеш в виде строки S вместе со строкой FKHash (будет рассмотрена далее) участвует в создании потокового шифра. Ниже приведён фрагмент текста программы:

// хешировать полученную "соль" с Psw в качестве пароля
H := CSA3Hash.Create;
H.SetPassword(Psw);
S := H.HashString(Salt, Cycles + 10000);

// создать потоковый шифр с ключом на основе полученного хеша
// и хеша файла-ключа (если не пустой)
SC := ShortCypher.Create;
SC.Gen(S + FKHash, Cycles);

При сохранении списка записей каждая запись вначале преобразуется в байтовый массив. В этот массив (через символы-разделители) вписываются все поля записи (Title, Site, Login, Categ), а затем присоединяется защищённая часть Prot. Затем весь массив шифруется полученным потоковым шифром и записывается в файл. Предварительно в этот файл нужно лишь вписать параметры сохраняемой записи: размер полей записи, размер защищённого массива Prot и величину Len. Чтение производится в обратной последовательности: читаются параметры записи, затем читается сама запись в массив, массив расшифровывается потоковым шифром и из него извлекаются поля записи (Title, Site, Login, Categ).
Также в конец файла записывается 32-битная контрольная сумма, которая считается перед шифрованием записей потоковым шифром. При чтении файла контрольная сумма считается после расшифровки записей потоковым шифром и позволяет убедиться в правильной расшифровке записей и целостности файла.
Как видно, в программе применяется двухуровневое шифрование двумя разными шифрами: блочным и потоковым. Блочный шифр создаётся непосредственно на основе мастер-пароля и необходим для шифрования добавляемых паролей, их истории и заметок. Потоковый шифр зависит не только от мастер-пароля, но и от случайной строки Salt, которая создаётся заново при каждом сохранении файла. Это обеспечивает в каждом случае создание уникального ключа шифрования. При этом менее защищённая часть (заголовки, логины и т.д.) шифруются только потоковым шифром, а пароли и заметки — и потоковым, и блочным, что обеспечивает повышенную защиту от возможного взлома.

Функции работы с файлом-ключом
Файл-ключ предназначен для повышения надёжности шифрования и усиления защиты от взлома файла-хранилища. Не зная мастер-пароль, но имея на руках защищённый файл, мастер-пароль можно попросту подобрать. Использование файла-ключа позволяет этого избежать (если этот файл хранится отдельно от файла с паролями).
Когда файл-ключ подключается к программе, вычисляется его хеш и помещается в строку FKHash. Эта строка (если она не пустая, когда файл-ключ не подключен) участвует в создании потокового шифра при открытии и сохранении файла паролей. При отсутствии файла-ключа становится практически невозможным создать правильный ключ потокового шифра и расшифровать сохранённые в файле записи.
В программе реализованы также функции создания файла-ключа. Одна из них создаёт файл-ключ, заполненный случайными байтами. Недостаток такого файла в том, что нужно обеспечивать его сохранность и при этом обязательно хранить его отдельно от файла-хранилища. Если такой файл-ключ будет утерян, восстановить его будет невозможно, как невозможно будет и открыть файл-хранилище.
Альтернативой является создание файла-ключа по паролю. Для этого создаётся шаблонный массив M размером FKSize и шифруется потоковым шифром, созданным на основе заданного пароля P. Ниже приведён фрагмент кода программы:

// заполнить массив шаблонными числами
for I := 0 to FKSize - 1 do
M[I] := I;

// создать шифр, зашифровать массив, освободить шифр
S := ShortCypher.Create;
S.Gen(P, Cycles);
S.EncryptMass(M, FKSize);
S.Free;

Полученный зашифрованный массив остаётся только записать в новый файл и файл-ключ готов. Преимущество этого способа в том, что беспокоиться о сохранности файл-ключа не нужно: в случае его утери такой же файл-ключ можно создать заново — нужно только помнить пароль или хранить его в надёжном месте. Ко всему прочему, полученный файл-ключ можно применять также в KeePassXC и некоторых других программах.

Другие функции и особенности
Как и в предыдущей версии, в PasswordKeeper 4 реализована функция замены мастер-пароля, генератор случайных паролей, поиск по записям, а также функция создания кода восстановления мастер-пароля (и восстановления мастер-пароля по этому коду). Но есть и некоторые различия. Так, из новой версии был исключён контроль доступа по PIN-коду. Теперь при каждом обращении к записям вводить PIN-код не требуется. Вместо этого реализована блокировка программы после некоторого периода неактивности, а для разблокировки необходимо опять вводить мастер-пароль.
Также из программы исключена виртуальная клавиатура для ввода паролей. Она могла бы быть полезной при наличии риска перехвата вводимых паролей. Однако при наличии такого риска лучше вообще не пользоваться программой либо использовать виртуальную клавиатуру сторонних разработчиков.
Другое нововведение — это работа с категориями. При добавлении каждого нового пароля для него можно указывать категорию, а потом в главном окне отображать записи только выбранных категорий — это повышает удобство работы с паролями. Поиск по пустой строке позволяет отобразить записи, не принадлежащие ни к одной из существующих категорий.
Создавать и открывать теперь можно произвольные файлы-хранилища, а не единственный установленный по умолчанию. При этом были исключены функции резервного копирования таких файлов: работа с резервными копиями должна производиться средствами операционной системы.
Значение количества циклов шифрования устанавливать при каждом создании файла-хранилища или замене мастер-пароля теперь не нужно: это значение установлено по умолчанию. Однако установленное по умолчанию значение можно изменить в настройках (изменение действует только до завершения программы или до следующего изменения).
Заключение
Как видно, все основные требования, поставленные перед новой версией PasswordKeeper, были выполнены. Применение среды разработки Lazarus позволяет относительно легко создавать сборки программы для разных операционных систем, в том числе Windows и Linux. Улучшенная схема работы с записями в виде упорядоченного списка позволила решить одновременно несколько задач. Во-первых, реализовать дополнительные поля записей, в частности, название сайта и логин. Во-вторых, позволила сортировать записи в алфавитном порядке ещё на этапе их добавления. В-третьих, это облегчило реализацию работы с категориями, которая сама по себе существенно облегчает работу с паролями и навигацию по ним.
Реализована также работа с историей паролей и некоторые другие полезные функции. Теперь при изменении пароля его прежняя версия сохраняется в истории. Историю можно открыть для просмотра, скопировать выбранный пароль в буфер обмена или удалить его (или даже очистить всю историю). Вместо неудобного ввода PIN-кода при каждом обращении к записям реализована блокировка открытого хранилища паролей после некоторого периода неактивности.
Кроме этого, в PasswordKeeper 4 была реализована более надёжная схема двухуровневого шифрования с применением блочного и потокового шифров. Схема генерации ключей шифрования устроена таким образом, что позволяет легко включать в неё файл-ключ кроме основного пароля (мастер-пароля). При этом реализованы две функции для создания файлов-ключей: одна создаёт случайный файл-ключ, а вторая — по заданному паролю.

Источники информации
1. Рафаэль Фахрутнидон. Google заявил о непричастности к утечке // Газета.ru [Электронный ресурс] — URL: https://www.gazeta.ru/tech/2018/07/05_a_11827645.shtml
2. Трунов Д.Н. Компьютерная программа PasswordKeeper для безопасного хранения паролей и заметок к ним. [Электронный ресурс] – URL: http://d-raft5.blogspot.com/2018/08/passwordkeeper.html
3. KeePassXC. The Project [Электронный ресурс] – URL: https://keepassxc.org/project
4. Overview of Free Pascal and Lazarus/ru [Электронный ресурс] – URL: https://wiki.lazarus.freepascal.org/Overview_of_Free_Pascal_and_Lazarus/ru
5. Методические указания и задания к лабораторным работам по курсу «Алгоритмы и структуры данных» (направление подготовки 6.050103 «Программная инженерия»). Сост.
Г.Г. Шалдырван, Н.С. костюкова. - Донецк, 2009. - 114 с.
6. Трунов Д.Н. От ESCK-5 к ESCK-6: основные отличия нового алгоритма шифрования. [Электронный ресурс] – URL: http://d-raft5.blogspot.com/2018/12/esck-5-esck-6.html
7. Трунов Д.Н. Реализация потокового шифра на основе алгоритма ESCK-6, его применение и оценки надёжности. [Электронный ресурс] – URL: https://d-raft5.blogspot.com/2019/06/esck-6.html
8. Трунов Д.Н. Алгоритм криптографической хеш-функции CSA-3 [Электронный ресурс] – URL: https://d-raft5.blogspot.com/2019/10/csa-3.html

суббота, 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)
Текст приложения приведён в оригинальном файле.

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

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