Предлагаемая обработка является продолжением другой, опубликованной здесь ранее:
Быстрый алгоритм шифрования AES ECB 128/192/256
Подпись данных и 1С
Платформа 1С позволяет подписать данные средствами платформы. Для этого необходимо использовать объект МенеджерКриптографии. Данный объект обеспечивает разработчика самым серьёзным инструментом по защите данных. Состав доступных алгоритмов определяется операционной системой и дополнительно установленными в ней модулями криптографии.
Для примера - ниже состав доступных алгоритмов с первых двух попавшихся ОС:
Таким образом перед разработчиком богатый выбор. Однако, обратите внимание - алгоритма AES в списке нет (128, 192, 256 .. никакого). Выдержка из википедии:
В июне 2003 года Агентство национальной безопасности США постановило, что шифр AES является достаточно надёжным, чтобы использовать его для защиты сведений, составляющих государственную тайну (англ. classified information). Вплоть до уровня SECRET было разрешено использовать ключи длиной 128 бит, для уровня TOP SECRET требовались ключи длиной 192 и 256 бит[8].
То есть не сказать, что он какой-то ничтожный.
Обмен данными между узлами через публичные каналы связи
Подпись данных применяется при авторизации данных в API-запросах. Суть метода заключается в том, чтобы подписать передаваемые данные в пакете и добавить отпечаток подписи в заголовок запроса, например в "X-API-Key". Через это обеспечивается дополнительная защита от подмены запросов.
Даже, если у злоумышленника будут данные авторизации пользователя, ему будет сложно подобрать ключ подписи.
Использование в этих целях стандартных средств требует дополнительных усилий:
1. Необходимо развернуть систему сертификатов на всех узлах.
2. Сертификаты нужно где-то получать и регулярно обновлять на всех узлах (устанавливать новые). Можно использовать самоподписанные сертификаты действием в 1 год, но такой вариант нежелателен.
3. Если вы "прохлопаете" сертификат - обмен остановится. Конечно, в случае, когда перед вами стоят очень высокие требования безопасности - это даже хорошо.
А можно что-то попроще?
Действительно - а если я просто хочу на всякий случай добавить подпись данных между своими узлами? Мне бы хотелось иметь один пароль или ключ, раздать его надёжным каналом по всем узлам и пользоваться. Ну там периодически менять его и всё. И желательно, чтоб это всё быстро работало.
Ответ: можно, но понадобится предлагаемая обработка.
Как работает подписание в предлагаемой обработке?
Достаточно просто - получаем хэш и шифруем:
1. На входе получаем подписываемые данные в виде строки или двоичных данных.
2. Получаем из них хэш SHA-256.
3. Далее получаем случайное число, 64 бит. Заполняем им выходной буфер (повторами).
4. Далее получаем отметку времени и XOR-им с серединкой выходного буфера.
5. XOR-им хэш с концом выходного буфера.
6. Шифруем всё секретным ключом.
Поясню пункты 1-5: На выходе получается такая цепочка: случайное число, далее XOR-енная отметка времени и далее XOR-енный хэш. Это нужно для того, чтобы было неизвестно, что зашифровано. Так сложнее организовать атаку с целью получения секретного ключа.
Для описанных ранее целей этого хватит с избытком.
Точно код подписи выглядит вот так:
// Функция - Подписать
//
// Параметры:
// Данные - ДвоичныеДанные, Строка - Подписываемые данные
// Ключ256 - ДвоичныеДанные - Ключ длинной 256 бит
//
// Возвращаемое значение:
// ДоичныеДанные - Отпечаток подписи
//
Функция Подписать(Данные, Ключ256) Экспорт
// Получаем хэш SHA-256
Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеш.Добавить(Данные);
SHA256 = Хеш.ХешСумма;
ЧтениеХэш = Новый ЧтениеДанных(SHA256);
БуферХэш = ЧтениеХэш.ПрочитатьВБуферДвоичныхДанных();
ЧтениеХэш.Закрыть();
// Формируем данные подписи:
// R = случайное число 8 байт
// D = время подписи в секундах XOR R
// X = Хэш данных XOR (R + R + R + R + R)
ВремяСозданияАлгоритма = 63739655820000; // Дата("20201030115700") - Дата("00010101") UTC
Сейчас = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяСозданияАлгоритма;
ГенераторСлучайныхЧисел = Новый ГенераторСлучайныхЧисел(Сейчас % 4294967295);
Ч1 = ГенераторСлучайныхЧисел.СлучайноеЧисло();
Ч2 = ГенераторСлучайныхЧисел.СлучайноеЧисло();
Буфер = Новый БуферДвоичныхДанных(48);
Буфер.ЗаписатьЦелое32(0, Ч1);
Буфер.ЗаписатьЦелое32(4, Ч2);
Буфер_R = Буфер.Скопировать();
Буфер.ЗаписатьЦелое64(8, Сейчас);
Буфер.Записать(16, БуферХэш, 32);
Буфер.ЗаписатьПобитовоеИсключительноеИли(8, Буфер_R, 8);
Буфер.ЗаписатьПобитовоеИсключительноеИли(16, Буфер_R, 8);
Буфер.ЗаписатьПобитовоеИсключительноеИли(24, Буфер_R, 8);
Буфер.ЗаписатьПобитовоеИсключительноеИли(32, Буфер_R, 8);
Буфер.ЗаписатьПобитовоеИсключительноеИли(40, Буфер_R, 8);
Поток = Новый ПотокВПамяти(Буфер);
ДанныеПодписи = Поток.ЗакрытьИПолучитьДвоичныеДанные();
Возврат ЗашифроватьAES(ДанныеПодписи, Ключ256);
КонецФункции // Подписать
При проверке подписи всё выполняется в обратном порядке:
// Функция - Проверить подпись
//
// Параметры:
// ПодписанныеДанные - Строка, ДвоичныеДанные - Подписанные данные
// ДанныеОтпечатка - ДвоичныеДанные - Отпечаток подписи
// Ключ256 - ДвоичныеДанные - Ключ подписи размером 256 бит
//
// Возвращаемое значение:
// Структура - Результат сверки подписи. Содержит структуру с полями:
// * ПодписьВерна - Булево - Результат проверки. Истина - всё в порядке.
// * ДатаПодписи - Дата - Дата подписания
// * ДоляСекунды - Число - Доля секунды, в которую происходило подписание.
//
Функция ПроверитьПодпись(ПодписанныеДанные, ДанныеОтпечатка, Ключ256) Экспорт
// Получаем хэш SHA-256
Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеш.Добавить(ПодписанныеДанные);
SHA256 = Хеш.ХешСумма;
ЧтениеХэш = Новый ЧтениеДанных(SHA256);
БуферХэш = ЧтениеХэш.ПрочитатьВБуферДвоичныхДанных();
ЧтениеХэш.Закрыть();
// Расшифровываем
ДанныеПодписи = РасшифроватьAES(ДанныеОтпечатка, Ключ256);
ЧтениеПодписи = Новый ЧтениеДанных(ДанныеПодписи);
БуферПодписи = ЧтениеПодписи.ПрочитатьВБуферДвоичныхДанных();
ЧтениеПодписи.Закрыть();
// Снимаем XOR
Буфер = Новый БуферДвоичныхДанных(48);
Буфер.Записать(0, БуферПодписи, 48);
Буфер.ЗаписатьПобитовоеИсключительноеИли(8, БуферПодписи, 8);
Буфер.ЗаписатьПобитовоеИсключительноеИли(16, БуферПодписи, 8);
Буфер.ЗаписатьПобитовоеИсключительноеИли(24, БуферПодписи, 8);
Буфер.ЗаписатьПобитовоеИсключительноеИли(32, БуферПодписи, 8);
Буфер.ЗаписатьПобитовоеИсключительноеИли(40, БуферПодписи, 8);
// Получаем результат
Результат = Новый Структура("ПодписьВерна,ДатаПодписи,ДоляСекунды");
Буфер.ЗаписатьПобитовоеИсключительноеИли(16, БуферХэш, 32);
КонтрольноеЧисло = Буфер.ПрочитатьЦелое64(16) + Буфер.ПрочитатьЦелое64(24) + Буфер.ПрочитатьЦелое64(32) + Буфер.ПрочитатьЦелое64(40);
Результат.ПодписьВерна = (КонтрольноеЧисло = 0);
Если Результат.ПодписьВерна Тогда
ВремяСозданияАлгоритма = 63739655820000; // Дата("20201030115700") - Дата("00010101") UTC
ВремяПодписи = Буфер.ПрочитатьЦелое64(8) + ВремяСозданияАлгоритма;
Результат.ДатаПодписи = Дата("00010101") + Цел(ВремяПодписи / 1000);
Результат.ДоляСекунды = ВремяПодписи % 1000;
КонецЕсли;
Возврат Результат;
КонецФункции // ПроверитьПодпись
Тесты
Обработка тестировалась на платформе 1С:Предприятие 8.3 (8.3.16.1502).
Сеанс выполнялся на ядре с тактовой частотой 1,58 ГГц (максимальная 3,3 ГГц).
За каждую итерацию выполняется подпись и проверка подписи - то есть два действия. В качестве подписываемых данных генерится строка, кратная одному блоку AES (16 байт): от 16 до 512 байт.
Для каждой длины делается 10 итераций и берётся среднее время.
Процедура тестирования:
&НаСервере
Процедура ТестСкоростиНаСервере()
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
СчётчикПаролей = 1;
ИсходнаяСтрока = "";
Для ЧислоБлоков = 1 по 32 Цикл
Данные = ПолучитьДанные(ЧислоБлоков);
СреднееВремя = 0;
Для НомерИтерации = 1 по 10 Цикл
Ключ256 = ПолучитьКлюч256(Формат(СчётчикПаролей, ""));
СчётчикПаролей = СчётчикПаролей + 1;
Время1 = ТекущаяУниверсальнаяДатаВМиллисекундах(); // СТАРТ!
Результат = ОбработкаОбъект.Подписать(Данные, Ключ256);
ИсходныеДанные = ОбработкаОбъект.ПроверитьПодпись(Данные, Результат, Ключ256);
Время2 = ТекущаяУниверсальнаяДатаВМиллисекундах(); // СТОП!
СреднееВремя = СреднееВремя + Время2 - Время1;
КонецЦикла;
ТекстСообщения = СтрШаблон(
"%1;%2"
,Формат(ЧислоБлоков, "ЧН=; ЧГ=")
,Формат(СреднееВремя / 10, "ЧРД=,; ЧН=; ЧГ=")
);
Сообщить(ТекстСообщения);
КонецЦикла;
КонецПроцедуры
Результат: