Как перенести шифрование HMAC-SHA256 и кодирование Base64URL из JavaScript/Python в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интеграции 1С с современными веб-сервисами, платежными шлюзами или при генерации JWT-токенов разработчики часто сталкиваются с необходимостью использовать криптографический алгоритм HMAC-SHA256 и специфический вид кодирования Base64URL. Готовые примеры в документации к API обычно написаны на JavaScript или Python, и прямая попытка переписать их на 1С «в лоб» часто вызывает трудности. Разберем детально, почему стандартные функции платформы могут давать другие результаты и как правильно реализовать эти механизмы в 1С.

В чем главные отличия алгоритмов в JS и 1С?

Для начала проанализируем ситуацию и выясним причины несовпадения результатов при обычном переносе кода:

  1. Особенности Base64URL: Стандартный алгоритм Base64, используемый встроенными функциями 1С Base64Строка() и Base64Значение(), не подходит для веб-ссылок из-за наличия символов +, / и знака выравнивания =. В спецификации Base64URL эти символы заменяются на безопасные для URL (- и _ соответственно), а хвостовые знаки равенства отбрасываются. Именно поэтому стандартные функции 1С без предварительной подготовки выдают некорректный для веб-сервиса результат.
  2. Суть алгоритма HMAC-SHA256: Обычное хеширование SHA-256 вычисляется от одной строки данных. Алгоритм HMAC (Hash-based Message Authentication Code) устроен гораздо сложнее: он подмешивает секретный ключ к сообщению, используя побитовое исключающее ИЛИ (XOR) со специальными константными масками (внутренней ipad и внешней opad), а затем дважды прогоняет полученный результат через хеш-функцию. Попытка последовательно передать ключ и данные через метод Добавить() объекта ХешированиеДанных в 1С приведет лишь к расчету обычного SHA-256 от соединенных байтов, что математически не имеет ничего общего с HMAC.

Рассмотрим подробнее четыре рабочих способа решения этой задачи в 1С: от простых «костылей» до красивого кроссплатформенного кода на чистом встроенном языке.

Способ 1. Реализация Base64URL на встроенном языке 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);
КонецФункции

Способ 2. Кроссплатформенный HMAC-SHA256 на двоичных буферах (Рекомендуемый)

Начиная с версии платформы 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(РезультатХешДД);
КонецФункции

Способ 3. Использование готовых функций БСП

Если ваша конфигурация построена на базе БСП (Библиотека Стандартных Подсистем), вам не нужно писать алгоритм HMAC вручную. В библиотеке уже есть готовый метод. Посмотрим на пример его использования:


// Декодируем входные данные
КлючДД = ДекодироватьBase64URL(СекретныйКлюч);
ВызовДД = ДекодироватьBase64URL(Вызов);

// Вызываем метод БСП (обычно находится в общем модуле криптографии БТС)
РезультатДД = РаботаВМоделиСервисаБТС.HMAC(КлючДД, ВызовДД, ХешФункция.SHA256, 64);

// Кодируем результат обратно в Base64URL
ИтоговаяСтрока = КодироватьBase64URL(РезультатДД);

Важное примечание: Всегда проверяйте актуальное имя общего модуля в вашей версии БСП, так как оно может незначительно отличаться в зависимости от релиза конфигурации.

Способ 4. Использование COM-объектов .NET (только под Windows)

Если ваша система работает исключительно под управлением операционной системы 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 данный код мгновенно перестанет работать.

Способ 5. Клиентский костыль с исполнением JS прямо в форме

Если вам нужно быстро решить задачу на стороне тонкого или толстого клиента под управлением 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). Они гарантируют высокую скорость работы, полную кроссплатформенность и независимость вашего кода от внешних факторов.

← На главную