При интеграции 1С с современными веб-сервисами, платежными шлюзами или при генерации JWT-токенов разработчики часто сталкиваются с необходимостью использовать криптографический алгоритм HMAC-SHA256 и специфический вид кодирования Base64URL. Готовые примеры в документации к API обычно написаны на JavaScript или Python, и прямая попытка переписать их на 1С «в лоб» часто вызывает трудности. Разберем детально, почему стандартные функции платформы могут давать другие результаты и как правильно реализовать эти механизмы в 1С.
Для начала проанализируем ситуацию и выясним причины несовпадения результатов при обычном переносе кода:
Base64Строка() и Base64Значение(), не подходит для веб-ссылок из-за наличия символов +, / и знака выравнивания =. В спецификации Base64URL эти символы заменяются на безопасные для URL (- и _ соответственно), а хвостовые знаки равенства отбрасываются. Именно поэтому стандартные функции 1С без предварительной подготовки выдают некорректный для веб-сервиса результат.Добавить() объекта ХешированиеДанных в 1С приведет лишь к расчету обычного SHA-256 от соединенных байтов, что математически не имеет ничего общего с HMAC.Рассмотрим подробнее четыре рабочих способа решения этой задачи в 1С: от простых «костылей» до красивого кроссплатформенного кода на чистом встроенном языке.
Прежде чем приступать к хешированию, подготовим функции для кодирования и декодирования Base64URL. Они потребуются нам в любом случае.
Для кодирования двоичных данных в строку Base64URL применим следующий алгоритм:
Функция КодироватьBase64URL(ДвоичныеДанные) Экспорт
// Получаем стандартный Base64
СтрокаBase64 = ПолучитьСтрокуBase64ИзДвоичныхДанных(ДвоичныеДанные);
// Заменяем символы под стандарт URL
СтрокаBase64URL = СтрЗаменить(СтрокаBase64, "+", "-");
СтрокаBase64URL = СтрЗаменить(СтрокаBase64URL, "/", "_");
// Удаляем символы выравнивания "=" в конце строки
СтрокаBase64URL = СтрЗаменить(СтрокаBase64URL, "=", "");
Возврат СтрокаBase64URL;
КонецФункции
Для обратного декодирования нам нужно восстановить замененные символы и обязательно дополнить строку символами =, чтобы ее длина была кратна 4, иначе платформенная функция декодирования вызовет ошибку:
Функция ДекодироватьBase64URL(Знач СтрокаBase64URL) Экспорт
// Восстанавливаем оригинальные символы Base64
СтрокаBase64 = СтрЗаменить(СтрокаBase64URL, "-", "+");
СтрокаBase64 = СтрЗаменить(СтрокаBase64, "_", "/");
// Восстанавливаем хвостовое выравнивание до кратности 4
Остаток = СтрДлина(СтрокаBase64) % 4;
Если Остаток = 2 Тогда
СтрокаBase64 = СтрокаBase64 + "==";
ИначеЕсли Остаток = 3 Тогда
СтрокаBase64 = СтрокаBase64 + "=";
КонецЕсли;
Возврат ПолучитьДвоичныеДанныеИзСтрокиBase64(СтрокаBase64);
КонецФункции
Начиная с версии платформы 8.3.6, разработчикам доступен мощный инструмент — БуферДвоичныхДанных. С его помощью мы можем производить любые побитовые операции, включая XOR, непосредственно на встроенном языке. Это позволяет написать полностью независимый алгоритм HMAC-SHA256, который будет одинаково успешно работать как на Windows, так и на Linux-серверах.
Разберем по шагам код такой функции:
Функция ВычислитьHMAC_SHA256(ДанныеДД, КлючДД) Экспорт
// Размер блока для алгоритма SHA-256 составляет 64 байта
РазмерБлока = 64;
// Шаг 1. Подготовка ключа шифрования
РабочийКлюч = Неопределено;
Если КлючДД.Размер() > РазмерБлока Тогда
// Если ключ длиннее 64 байт, хешируем его
Хеширование = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеширование.Добавить(КлючДД);
РабочийКлюч = Хеширование.ХешСумма;
Иначе
РабочийКлюч = КлючДД;
КонецЕсли;
// Создаем буфер для ключа и заполняем его нулями до 64 байт
БуферКлюча = Новый БуферДвоичныхДанных(РазмерБлока);
БуферКлюча.Записать(0, РабочийКлюч);
// Шаг 2. Подготовка буферов для масок ipad и opad
БуферIpad = Новый БуферДвоичныхДанных(РазмерБлока);
БуферOpad = Новый БуферДвоичныхДанных(РазмерБлока);
// Заполняем маски константами: ipad = 0x36 (54), opad = 0x5c (92)
Для Индекс = 0 По РазмерБлока - 1 Цикл
БуферIpad.Записать(Индекс, 54);
БуферOpad.Записать(Индекс, 92);
КонецЦикла;
// Применяем операцию XOR между подготовленным ключом и масками
БуферIpad.ЗаписатьПобитовоеИсключительноеИли(0, БуферКлюча);
БуферOpad.ЗаписатьПобитовоеИсключительноеИли(0, БуферКлюча);
// Шаг 3. Вычисление внутреннего хеша: SHA256(ipad + Данные)
ВнутренниеДанные = ОбъединитьДвоичныеДанные(БуферIpad.ПолучитьДвоичныеДанные(), ДанныеДД);
ХешированиеВнутреннее = Новый ХешированиеДанных(ХешФункция.SHA256);
ХешированиеВнутреннее.Добавить(ВнутренниеДанные);
ВнутреннийХеш = ХешированиеВнутреннее.ХешСумма;
// Шаг 4. Вычисление внешнего хеша (финальный HMAC): SHA256(opad + ВнутреннийХеш)
ВнешниеДанные = ОбъединитьДвоичныеДанные(БуферOpad.ПолучитьДвоичныеДанные(), ВнутреннийХеш);
ХешированиеВнешнее = Новый ХешированиеДанных(ХешФункция.SHA256);
ХешированиеВнешнее.Добавить(ВнешниеДанные);
Возврат ХешированиеВнешнее.ХешСумма;
КонецФункции
Функция ОбъединитьДвоичныеДанные(Данные1, Данные2)
Буфер = Новый БуферДвоичныхДанных(Данные1.Размер() + Данные2.Размер());
Буфер.Записать(0, Данные1);
Буфер.Записать(Данные1.Размер(), Данные2);
Возврат Буфер.ПолучитьДвоичныеДанные();
КонецФункции
Теперь мы можем собрать весь процесс воедино, полностью повторив логику исходной JS-функции CreateChallengeResponse:
Функция ПолучитьОтветНаВызов(СекретныйКлючСтрока, ВызовСтрока) Экспорт
// Декодируем входящие Base64URL параметры в бинарный вид
КлючДД = ДекодироватьBase64URL(СекретныйКлючСтрока);
ВызовДД = ДекодироватьBase64URL(ВызовСтрока);
// Вычисляем HMAC-SHA256
РезультатХешДД = ВычислитьHMAC_SHA256(ВызовДД, КлючДД);
// Кодируем результат обратно в формат Base64URL
Возврат КодироватьBase64URL(РезультатХешДД);
КонецФункции
Если ваша конфигурация построена на базе БСП (Библиотека Стандартных Подсистем), вам не нужно писать алгоритм HMAC вручную. В библиотеке уже есть готовый метод. Посмотрим на пример его использования:
// Декодируем входные данные
КлючДД = ДекодироватьBase64URL(СекретныйКлюч);
ВызовДД = ДекодироватьBase64URL(Вызов);
// Вызываем метод БСП (обычно находится в общем модуле криптографии БТС)
РезультатДД = РаботаВМоделиСервисаБТС.HMAC(КлючДД, ВызовДД, ХешФункция.SHA256, 64);
// Кодируем результат обратно в Base64URL
ИтоговаяСтрока = КодироватьBase64URL(РезультатДД);
Важное примечание: Всегда проверяйте актуальное имя общего модуля в вашей версии БСП, так как оно может незначительно отличаться в зависимости от релиза конфигурации.
Если ваша система работает исключительно под управлением операционной системы Windows, вы можете использовать COM-объекты .NET Framework для доступа к системной криптографии:
Функция ВычислитьHMAC_SHA256_COM(ДанныеСтрока, КлючСтрока)
Кодировщик = Новый COMОбъект("System.Text.UTF8Encoding");
ХешАлгоритм = Новый COMОбъект("System.Security.Cryptography.HMACSHA256");
// Устанавливаем ключ шифрования
ХешАлгоритм.Key = Кодировщик.GetBytes_4(КлючСтрока);
// Вычисляем хеш-сумму
БайтыХеша = ХешАлгоритм.ComputeHash_2(Кодировщик.GetBytes_4(ДанныеСтрока)).Выгрузить();
// Преобразуем массив байтов .NET в двоичные данные 1С
Буфер = Новый БуферДвоичныхДанных(БайтыХеша.Количество());
Для Индекс = 0 По БайтыХеша.Количество() - 1 Цикл
Буфер.Записать(Индекс, БайтыХеша[Индекс]);
КонецЦикла;
Возврат Буфер.ПолучитьДвоичныеДанные();
КонецФункции
Этот способ лаконичен, но помните о его существенном недостатке: при переносе сервера 1С на операционную систему Linux данный код мгновенно перестанет работать.
Если вам нужно быстро решить задачу на стороне тонкого или толстого клиента под управлением Windows, можно использовать встроенное поле HTML-документа, как советовали на форуме в сообщении 21. Данный подход выполняет исходный JS-код во внутреннем движке браузера:
&НаКлиенте
Процедура ВычислитьЧерезJS()
HTMLКод = "
|<html>
|<head>
| <script src=""https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/hmac-sha256.js""></script>
| <script src=""https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/enc-base64-min.js""></script>
|</head>
|<body>
| <div id=""res""></div>
| <script>
| // Тут размещаем JS-функции base64url_encode, base64url_decode и CreateChallengeResponse
| var res = CreateChallengeResponse('" + СекретныйКлюч + "', '" + Вызов + "');
| document.getElementById('res').innerText = res;
| </script>
|</body>
|</html>";
// Записываем код в реквизит формы, связанный с Полем HTML-документа
СодержимоеHTML = HTMLКод;
КонецПроцедуры
&НаКлиенте
Процедура СодержимоеHTMLДокументСформирован(Элемент)
// Извлекаем результат выполнения скрипта из DOM-дерева
Результат = Элементы.СодержимоеHTML.Документ.getElementById("res").innerText;
Сообщить("Результат вычисления: " + Результат);
КонецПроцедуры
Использовать этот метод на постоянной основе в серверном коде категорически не рекомендуется. Он сильно замедляет работу системы, требует наличия интерактивной формы и полностью завязан на доступность внешних CDN-серверов с библиотеками CryptoJS, что делает систему уязвимой при отсутствии интернета.
Мы детально разобрали особенности работы криптографии в 1С. Наиболее надежным и архитектурно правильным решением является Способ 2 (на чистом встроенном языке с использованием буферов двоичных данных) или использование готовых методов БСП (Способ 3). Они гарантируют высокую скорость работы, полную кроссплатформенность и независимость вашего кода от внешних факторов.