Как реализовать подпись HMAC-SHA256 и перенести авторизацию POST-запроса из C# в 1С?

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

При интеграции 1С со сторонними сервисами (биржами, торговыми площадками, платежными шлюзами) разработчики часто сталкиваются с необходимостью перенести существующий код авторизации с языков C#, Python или PHP на встроенный язык 1С. Одной из самых частых проблем становится формирование подписи HMAC-SHA256 и верное кодирование передаваемых данных.

Рассмотрим подробно, как перевести алгоритм подписи и отправить HTTP-запрос в 1С, а также проанализируем основные подводные камни, которые могут привести к ошибке авторизации на стороне сервера.

Анализ исходного алгоритма C# и ключевые подводные камни

Разберем по шагам, что происходит в исходном C#-коде:

  1. Хеширование тела запроса (MD5): Содержимое тела POST-запроса хешируется с помощью MD5, а результат переводится в Base64-строку.
  2. Генерация Nonce: Создается уникальная одноразовая строка (UUID/GUID без дефисов).
  3. Формирование подписываемой строки: Объединяются публичный ключ (apiKey), метод HTTP (POST), закодированный URI в нижнем регистре, nonce и MD5-хеш тела.
  4. Декодирование секретного ключа: Секретный ключ apiSecret декодируется из формата Base64 в массив байтов. Это критически важный момент: если использовать сам текст секретного ключа без предварительного Base64-декодирования, сформированная подпись не пройдет проверку на сервере.
  5. Вычисление HMAC-SHA256: С помощью декодированного ключа и алгоритма HMAC-SHA256 подписывается сформированная строка, а полученный результат снова кодируется в Base64.

Выясним основные причины, почему подпись, сформированная в 1С, может не совпадать с подписью из C#:

1. Кодировка строк: Все текстовые строки перед вычислением хеш-сумм должны переводиться в двоичные данные строго в кодировке UTF-8 без сигнатуры BOM.

2. Правила URL Encode: В C# метод HttpUtility.UrlEncode приводит специальный набор символов к определенному формату и затем переводит URI в нижний регистр .ToLower(). В 1С кодирование спецсимволов функции КодироватьСтроку должно строго соответствовать ожиданиям стороннего API.

Вариант 1. Использование .NET через COM-объект (для Windows)

Если сервер 1С или клиентский сеанс работает под управлением ОС Windows, мы можем применить стандартные классы .NET Framework прямо из кода 1С с помощью механизмов COM. Это решение максимально точно воспроизводит логику C#.

Рассмотрим пример функции вычисления HMAC-SHA256 через COM-объекты .NET:


Функция ПолучитьHMACSHA256_NET(СтрокаСообщения, СекретBase64)
    
    // Создаем объекты .NET для работы с UTF-8 и HMAC-SHA256
    КодировщикUTF8 = Новый COMОбъект("System.Text.UTF8Encoding");
    
    // Декодируем Base64-секрет в массив байт, как в C# (Convert.FromBase64String)
    ОбъектConvert = Новый COMОбъект("System.Convert");
    ДвоичныйСекрет = ОбъектConvert.FromBase64String(СекретBase64);
    
    // Инициализируем HMACSHA256 с декодированным ключом
    HMAC = Новый COMОбъект("System.Security.Cryptography.HMACSHA256");
    HMAC.Key = ДвоичныйСекрет;
    
    // Переводим подписываемую строку в байты UTF-8
    БайтыСообщения = КодировщикUTF8.GetBytes_4(СтрокаСообщения);
    
    // Вычисляем хеш
    БайтыХеша = HMAC.ComputeHash_2(БайтыСообщения);
    
    // Возвращаем результат в формате Base64
    Возврат ОбъектConvert.ToBase64String(БайтыХеша);
    
КонецФункции

Обратите внимание: Данный способ работает быстро и надежно, но ограничен исключительно операционной системой Windows. На серверах под управлением Linux данный метод вызовет ошибку создания COM-объекта.

Вариант 2. Кроссплатформенная реализация на чистом BSL

Для обеспечения универсальности (работа в Linux и облачных сервисах) разберем создание HMAC-SHA256 с использованием стандартных объектов 1С: ХешированиеДанных и БуферДвоичныхДанных.

Алгоритм HMAC строится на выполнении побитовой операции исключающего ИЛИ (XOR) для ключа с константами ipad (байты 0x36) и opad (байты 0x5C).


Функция ПолучитьHMACSHA256_BSL(СтрокаСообщения, СекретBase64)
    
    // 1. Получаем двоичные данные из Base64 ключа
    ДвоичныйКлюч = ПолучитьДвоичныеДанныеИзBase64Строки(СекретBase64);
    БуферКлюча = Новый БуферДвоичныхДанных(ДвоичныйКлюч);
    
    РазмерБлока = 64; // Размер блока SHA-256 в байтах
    
    If БуферКлюча.Количество() > РазмерБлока Тогда
        // Если ключ длиннее размера блока, хешируем его
        ХешерКлюча = Новый ХешированиеДанных(ХешФункция.SHA256);
        ХешерКлюча.Добавить(БуферКлюча);
        БуферКлюча = Новый БуферДвоичныхДанных(ХешерКлюча.ХешСумма);
    EndIf;
    
    // Дополняем ключ нулями до длины блока
    РабочийКлюч = Новый БуферДвоичныхДанных(РазмерБлока);
    БуферКлюча.Копировать(0, РабочийКлюч, 0, БуферКлюча.Количество());
    
    // Создаем внутренний (IPAD) и внешний (OPAD) буферы
    БуферIPAD = Новый БуферДвоичныхДанных(РазмерБлока);
    БуферOPAD = Новый БуферДвоичныхДанных(РазмерБлока);
    
    Для Индекс = 0 По РазмерБлока - 1 Цикл
        БайтКлюча = РабочийКлюч[Индекс];
        БуферIPAD[Индекс] = БайтКлюча ВРазряд = 0x36;
        БуферOPAD[Индекс] = БайтКлюча ВРазряд = 0x5C;
    КонецЦикла;
    
    // 2. Внутренний хеш: SHA256(IPAD + Сообщение)
    ДвоичныеДанныеСообщения = ПолучитьДвоичныеДанныеИзСтроки(СтрокаСообщения, КодировкаТекста.UTF8, Ложь);
    
    ПотокПамяти = Новый ПотокВПамяти;
    ПотокПамяти.Записать(БуферIPAD, 0, РазмерБлока);
    ПотокПамяти.Записать(Новый БуферДвоичныхДанных(ДвоичныеДанныеСообщения), 0, ДвоичныеДанныеСообщения.Размер());
    ПотокПамяти.Перейти(0, СмещениеПотока.Начало);
    
    ХешерВнутренний = Новый ХешированиеДанных(ХешФункция.SHA256);
    ХешерВнутренний.Добавить(ПотокПамяти);
    ВнутреннийХеш = ХешерВнутренний.ХешСумма;
    
    // 3. Внешний хеш: SHA256(OPAD + ВнутреннийХеш)
    ПотокПамятиВнешний = Новый ПотокВПамяти;
    ПотокПамятиВнешний.Записать(БуферOPAD, 0, РазмерБлока);
    ПотокПамятиВнешний.Записать(Новый БуферДвоичныхДанных(ВнутреннийХеш), 0, ВнутреннийХеш.Размер());
    ПотокПамятиВнешний.Перейти(0, СмещениеПотока.Начало);
    
    ХешерВнешний = Новый ХешированиеДанных(ХешФункция.SHA256);
    ХешерВнешний.Добавить(ПотокПамятиВнешний);
    
    // 4. Возвращаем Итоговый HMAC в Base64
    Возврат ПолучитьBase64СтрокуИзДвоичныхДанных(ХешерВнешний.ХешСумма);
    
КонецФункции

Полная сборка POST-запроса в 1С

Теперь объединим подготовку данных, расчет MD5 тела, генерацию Nonce и отправку запроса через объект HTTPСоединение.


Функция ОтправитьЗапросApi(URLЗапроса, APIKey, APISecret, СтруктураТела)
    
    // Переводим тело запроса в JSON
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, СтруктураТела);
    СтрокаJSON = ЗаписьJSON.Закрыть();
    
    // 1. Хеш MD5 от тела запроса в Base64
    ДвоичныеДанныеТела = ПолучитьДвоичныеДанныеИзСтроки(СтрокаJSON, КодировкаТекста.UTF8, Ложь);
    ХешерMD5 = Новый ХешированиеДанных(ХешФункция.MD5);
    ХешерMD5.Добавить(ДвоичныеДанныеТела);
    MD5Base64 = ПолучитьBase64СтрокуИзДвоичныхДанных(ХешерMD5.ХешСумма);
    
    // 2. Генерация Nonce (GUID без разделителей)
    Nonce = СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "");
    
    // 3. Подготовка URL и сырой подписываемой строки
    КодированныйURL = НРегистр(КодироватьСтроку(URLЗапроса, СпособКодированияСтроки.URLКодированиеЗаголовков));
    СтрокаСигнатуры = APIKey + "POST" + КодированныйURL + Nonce + MD5Base64;
    
    // 4. Расчет подписи HMAC-SHA256
    HMACПодпись = ПолучитьHMACSHA256_BSL(СтрокаСигнатуры, APISecret);
    
    // 5. Формирование HTTP Запроса
    Заголовки = Новый Соответствие;
    Заголовки.Вставить("Content-Type", "application/json");
    
    // Заголовок авторизации вида "amx key:signature:nonce"
    ЗначениеAuth = СтрШаблон("%1:%2:%3", APIKey, HMACПодпись, Nonce);
    Заголовки.Вставить("Authorization", "amx " + ЗначениеAuth);
    
    HTTPЗапрос = Новый HTTPЗапрос(URLЗапроса, Заголовки);
    HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
    
    // 6. Отправка
    Соединение = Новый HTTPСоединение("www.aaa.ru", 443, , , , , Новый ЗащищенноеСоединениеOpenSSL);
    Ответ = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
    
    Возврат Ответ.ПолучитьТелоКакСтроку();
    
КонецФункции

Таким образом, мы полностью воссоздали алгоритм работы оригинального C#-кода. При использовании чистого кода BSL ваша обработка или торговый робот смогут успешно выполнять авторизацию на любых платформах под управлением Windows и Linux.

← На главную