Почему расходится шифрование HMAC-SHA512 в 1С и PHP и как это исправить

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

При интеграции систем 1С с внешними сервисами на PHP часто возникает необходимость формировать подпись запросов по алгоритму HMAC-SHA512. Однако разработчики регулярно сталкиваются с ситуацией, когда одинаковый исходный текст и секретный ключ выдают абсолютно разные хэши в 1С и в PHP.

В этой статье мы подробно разберем основные причины расхождений, проанализируем ошибки работы с кодировками и двоичными данными в 1С, а также рассмотрим правильный способ расчета хэша HMAC-SHA512.

Причина 1: Обработка спецсимволов в PHP

Разберем сначала простую, но распространенную синтаксическую ошибку на стороне PHP. Если передавать подписываемую строку в двойных кавычках, например "te$st", интерпретатор PHP воспринимает конструкцию $st как переменную. В результате на выходе получается строка "te" вместо "te$st".

Для корректного сравнения с 1С на стороне PHP необходимо экранировать знак доллара или использовать одинарные кавычки:

Неправильно в PHP:

$sign = hash_hmac('sha512', "te$st", "123");

Правильно в PHP:

$sign = hash_hmac('sha512', 'te$st', '123');

Или с экранированием:

$sign = hash_hmac('sha512', "te\$st", "123");

Причина 2: Искажение двоичного ключа при преобразовании в строку 1С

Более глубокая техническая проблема возникает при работе с секретным ключом, закодированным в Base64. Очень часто секретные ключи подписи передаются в формате Base64, например: sO017E2K/s0VLtKNxd5HwOYbRdt/sA1Bv0QECa0+Wv7CHKDyKG0o7puTvP8/OQA/Nr3dCvPUpztzOKc6/wXj+g==.

В PHP функция base64_decode() превращает такую строку в «сырой» массив байт (сырую бинарную строку):

$secret = base64_decode($key);

В 1С разработчики часто совершают ошибку, сначала декодируя Base64 через Base64Значение(), а затем преобразуя получившиеся ДвоичныеДанные в обычную строку с помощью ПолучитьСтрокуИзДвоичныхДанных() или чтения текстовым документом.

Почему так делать нельзя: произвольный массив байт (значения от 0x00 до 0xFF) не является валидным текстом в кодировке UTF-8 или ANSI. При попытке интерпретировать сырые байты как текст происходят необратимые искажения: служебные символы перевода строк (0x0D, 0x0A) и некорректные байтовые последовательности заменяются или теряются. Когда эта искаженная строка снова переводится в байты через System.Text.UTF8Encoding, получаются совершенно другие байты ключа, чем в PHP.

Правильная реализация HMAC-SHA512 в 1С с передачей байтового массива

Чтобы хэш совпал с PHP, секретный ключ после декодирования Base64 должен передаваться в COM-объект System.Security.Cryptography.HMACSHA512 непосредственно как массив байт (COMSafeArray с типом VT_UI1), без какого-либо перевода в строковый тип 1С.

Рассмотрим пошаговое решение этой задачи.

  1. Декодируем Base64-строку в объект ДвоичныеДанные.
  2. Преобразуем двоичные данные в массив десятичных чисел (байтов).
  3. Упаковываем массив в COMSafeArray с типом элементов VT_UI1.
  4. Передаем получившийся массив байт напрямую в свойство Key COM-объекта HMACSHA512.

Ниже приведен готовый пример кода для 1С:


Функция ХешФункцияSHA512_Base64Ключ(Знач Строка, Знач СекретныйКлючBase64) Экспорт
    
    // 1. Создаем COM-объекты .NET
    Encoding = Новый COMОбъект("System.Text.UTF8Encoding");
    HMAC = Новый COMОбъект("System.Security.Cryptography.HMACSHA512");
    
    // 2. Получаем двоичные данные ключа из Base64
    ДвоичныеДанныеКлюча = Base64Значение(СекретныйКлючBase64);
    
    // 3. Преобразуем ДвоичныеДанные в COMSafeArray (массив байтов)
    МассивБайтКлюча = ДвоичныеДанныеВБайтыCOM(ДвоичныеДанныеКлюча);
    HMAC.Key = МассивБайтКлюча;
    
    // 4. Вычисляем хэш от исходной строки
    БайтыСтроки = Encoding.GetBytes_4(Строка);
    ХешБайт = HMAC.ComputeHash_2(БайтыСтроки).Выгрузить();
    
    // 5. Переводим результат в шестнадцатеричную строку (hex)
    Хеш = "";
    Для Каждого Байт Из ХешБайт Цикл
        Символ = DecToHex(Байт);
        Хеш = Хеш + ДобавитьЛидирующиеНули(Символ, 2);
    КонецЦикла;
    
    Возврат Хеш;
    
КонецФункции

Функция ДвоичныеДанныеВБайтыCOM(ДвоичныеДанные)
    
    // Переводим двоичные данные в Hex-строку и разбиваем по байтам
    ХексСтрока = ЗначениеВHexСтроку(ДвоичныеДанные);
    МассивБайт = Новый Массив;
    
    Длина = СтрДлина(ХексСтрока);
    Для Индекс = 1 По Длина / 2 Цикл
        БайтHex = Сред(ХексСтрока, (Индекс - 1) * 2 + 1, 2);
        МассивБайт.Добавить(HexToDec(БайтHex));
    КонецЦикла;
    
    // Возвращаем COMSafeArray с типом элементов VT_UI1 (unsigned 8-bit integer)
    Возврат Новый COMSafeArray(МассивБайт, "VT_UI1", МассивБайт.Количество());
    
КонецФункции

Функция HexToDec(Знач HexСтрока)
    
    СоответствиеHEX = Новый Соответствие;
    СоответствиеHEX.Вставить("0", 0);  СоответствиеHEX.Вставить("1", 1);
    СоответствиеHEX.Вставить("2", 2);  СоответствиеHEX.Вставить("3", 3);
    СоответствиеHEX.Вставить("4", 4);  СоответствиеHEX.Вставить("5", 5);
    СоответствиеHEX.Вставить("6", 6);  СоответствиеHEX.Вставить("7", 7);
    СоответствиеHEX.Вставить("8", 8);  СоответствиеHEX.Вставить("9", 9);
    СоответствиеHEX.Вставить("A", 10); СоответствиеHEX.Вставить("B", 11);
    СоответствиеHEX.Вставить("C", 12); СоответствиеHEX.Вставить("D", 13);
    СоответствиеHEX.Вставить("E", 14); СоответствиеHEX.Вставить("F", 15);
    СоответствиеHEX.Вставить("a", 10); СоответствиеHEX.Вставить("b", 11);
    СоответствиеHEX.Вставить("c", 12); СоответствиеHEX.Вставить("d", 13);
    СоответствиеHEX.Вставить("e", 14); СоответствиеHEX.Вставить("f", 15);
    
    Старший = СоответствиеHEX[Сред(HexСтрока, 1, 1)];
    Младший = СоответствиеHEX[Сред(HexСтрока, 2, 1)];
    
    Возврат Старший * 16 + Младший;
    
КонецФункции

Функция DecToHex(Знач Число)
    Результат = "";
    Пока Число <> 0 Цикл
        Поз = Число % 16;
        Результат = Сред("0123456789abcdef", Поз + 1, 1) + Результат;
        Число = Цел(Число / 16);
    КонецЦикла;
    Возврат Результат;
КонецФункции

Функция ДобавитьЛидирующиеНули(Знач Строка, ЧислоСимволов)
    ВозвращаемаяСтрока = Строка(Строка);
    Длина = СтрДлина(ВозвращаемаяСтрока);
    Для й = 1 По ЧислоСимволов - Длина Цикл
        ВозвращаемаяСтрока = "0" + ВозвращаемаяСтрока;
    КонецЦикла;
    Возврат ВозвращаемаяСтрока;
КонецФункции

Ограничения использования COM-объектов .NET

Проанализируем важный нюанс: использование COM-объектов System.Security.Cryptography работает исключительно на серверах под управлением ОС Windows. Если ваш сервер 1С запущен на Linux или вы разработали мобильное приложение, данный подход вызовет ошибку отсутствия COM-компонента.

Для кроссплатформенной работы на Linux-серверах можно использовать следующие альтернативы:

  1. Вызов консольной утилиты OpenSSL через метод ЗапуститьПриложение().
  2. Использование внешних компонент (Native API), написанных на C++ или Rust.
  3. Реализация алгоритма HMAC вручную на 1С с использованием штатного объекта ХешированиеДанных (функция ХешФункция.SHA512) путем создания двух проходов хеширования с ключом (ipad и opad).
← На главную