Как рассчитать подпись данных алгоритмом HMAC-SHA256 в 1С для совместимости с PHP?

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

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

Анализ начальной проблемы: почему простая конкатенация не работает

Многие разработчики, впервые сталкиваясь с задачей создания подписи, могут ошибочно предполагать, что HMAC-SHA256 — это просто хеширование конкатенированных данных и секретного ключа. Рассмотрим пример первой попытки, описанной в сообщении 1:


ХешированиеДанных = Новый ХешированиеДанных(ХешФункция.SHA256);
ХешированиеДанных.Добавить(СтрокаСДанными);
ХешированиеДанных.Добавить(Секретныйключ);
Подпись = НРег(СтрЗаменить(ХешированиеДанных.ХешСумма, " ", ""));

В этом коде мы пытаемся создать хеш SHA256 от строки, которая является простой конкатенацией СтрокаСДанными и Секретныйключ. Например, если СтрокаСДанными = "Это текст" и СекретныйКлюч = "123", то будет хеширована строка "Это текст123". При этом ХешированиеДанных.ХешСумма возвращает шестнадцатеричную строку, которую затем преобразуют к нижнему регистру и удаляют пробелы.

Однако, как было верно отмечено в сообщении 5, HMAC-SHA256 — это не просто конкатенация данных с ключом, а предварительная XOR обработка их перед шифрованием. Это принципиальное отличие. HMAC (Hash-based Message Authentication Code) использует секретный ключ для создания двух производных ключей (внутреннего и внешнего). Затем он выполняет два прохода хеширования: сначала хешируется сообщение с внутренним ключом, а затем результат этого хеша хешируется с внешним ключом. Такая схема обеспечивает гораздо более высокий уровень безопасности и устойчивость к различным атакам по сравнению с простым хешированием конкатенированных данных.

Именно поэтому начальный подход с использованием Новый ХешированиеДанных(ХешФункция.SHA256) и простой конкатенации данных и ключа приводит к несовпадающим подписям при проверке в другой системе (например, PHP, где используется функция hash_hmac(), которая реализует правильный алгоритм HMAC).

Корректное решение в 1С: использование встроенной функции HMAC-SHA256

К счастью, в 1С, особенно в современных конфигурациях, основанных на БСП (Библиотека стандартных подсистем), существует готовая функция для расчета HMAC-SHA256. Это значительно упрощает задачу и исключает необходимость реализации сложного алгоритма вручную. Как указано в сообщении 2, такую функцию можно найти в общем модуле РаботаВМоделиСервисаБТС.

Функция называется Подпись и имеет следующий синтаксис (или похожий):

РаботаВМоделиСервисаБТС.Подпись(Ключ, ДанныеДляПодписи)

Важно отметить особенности этой функции:

  1. Параметр Ключ ожидает двоичные данные.
  2. Параметр ДанныеДляПодписи ожидает строку.
  3. Функция возвращает подпись в формате Base64.

Таким образом, для ее корректного использования нам необходимо выполнить следующие шаги:

Шаг 1: Преобразование секретного ключа в двоичные данные

Поскольку РаботаВМоделиСервисаБТС.Подпись ожидает ключ в виде двоичных данных, нам нужно преобразовать строковый секретный ключ. Для этого используется метод ПолучитьДвоичныеДанныеИзСтроки(), доступный в 1С начиная с версии 8.3.10. Важно при этом указать кодировку, которая будет соответствовать кодировке, используемой в системе-получателе (PHP).


// Предположим, что СекретныйКлюч изначально строка
// Важно: укажите ту кодировку, которую ожидает ваша PHP-система, обычно это UTF-8
СекретныйКлючДвоичныеДанные = ПолучитьДвоичныеДанныеИзСтроки(СекретныйКлюч, КодировкаТекста.UTF8);

Если данные для подписи также являются строкой, как и в большинстве случаев, они могут быть переданы напрямую, так как функция РаботаВМоделиСервисаБТС.Подпись принимает данные в строковом формате. Однако, мы всегда должны быть уверены в кодировке этих данных.

Шаг 2: Вызов функции подписи

После подготовки ключа, мы можем вызвать функцию Подпись:


// СтрокаСДанными - это строка, которую мы хотим подписать
// СекретныйКлючДвоичныеДанные - это ключ, преобразованный в двоичные данные
ПодписьBase64 = РаботаВМоделиСервисаБТС.Подпись(СекретныйКлючДвоичныеДанные, СтрокаСДанными);

Переменная ПодписьBase64 теперь будет содержать HMAC-SHA256 подпись в формате Base64.

Шаг 3: Преобразование формата подписи (при необходимости)

Функция РаботаВМоделиСервисаБТС.Подпись возвращает результат в Base64. Однако, при интеграции с PHP, функция hash_hmac() по умолчанию возвращает шестнадцатеричную строку (hex). Чтобы подписи совпали, необходимо привести их к одному формату. Обычно PHP-сторона ожидает либо hex, либо Base64.

Если PHP ожидает шестнадцатеричную строку (как это часто бывает, когда hash_hmac вызывается без параметра raw_output = true), нам нужно преобразовать Base64-строку из 1С в hex-строку. Для этого сначала переводим Base64 в двоичные данные, а затем получаем из них шестнадцатеричное представление. Пример из сообщения 2:


// ПодписьBase64 получена из РаботаВМоделиСервисаБТС.Подпись
ДвоичныеДанныеПодписи = Base64Значение(ПодписьBase64);
ПодписьHex = НРег(СтрЗаменить(ДвоичныеДанныеПодписи.ПолучитьСтрокуШестнадцатеричныхЧисел(), " ", ""));

Функция ПолучитьСтрокуШестнадцатеричныхЧисел() возвращает строку, где каждый байт представлен двумя шестнадцатеричными символами, разделенными пробелами. Поэтому, как и в первом сообщении, мы используем НРег(СтрЗаменить(..., " ", "")) для получения компактной строки в нижнем регистре.

Если же PHP ожидает Base64, то преобразование не требуется, и можно использовать ПодписьBase64 напрямую.

Полный пример кода в 1С


// Исходные данные
СтрокаСДанными = "Это данные, которые нужно подписать.";
СекретныйКлюч = "МойСуперСекретныйКлючДляHMAC";

// 1. Преобразуем секретный ключ в двоичные данные
// Важно: Указываем кодировку UTF8, так как она часто используется для обмена данными и в PHP
СекретныйКлючДвоичныеДанные = ПолучитьДвоичныеДанныеИзСтроки(СекретныйКлюч, КодировкаТекста.UTF8);

// 2. Вызываем функцию HMAC-SHA256 из БСП
// РаботаВМоделиСервисаБТС.Подпись возвращает результат в Base64
ПодписьBase64 = РаботаВМоделиСервисаБТС.Подпись(СекретныйКлючДвоичныеДанные, СтрокаСДанными);

// 3. Преобразуем Base64 в Hex-строку (если это необходимо для совместимости с PHP,
// где hash_hmac по умолчанию возвращает hex)
ДвоичныеДанныеПодписи = Base64Значение(ПодписьBase64);
ПодписьДляPHP = НРег(СтрЗаменить(ДвоичныеДанныеПодписи.ПолучитьСтрокуШестнадцатеричныхЧисел(), " ", ""));

Сообщить("Подпись (Base64): " + ПодписьBase64);
Сообщить("Подпись (Hex для PHP): " + ПодписьДляPHP);

Особенности работы с кодировками: критический момент

Одна из наиболее частых причин несовпадения подписей между разными системами — это различия в кодировке строк. HMAC работает с байтами, поэтому крайне важно, чтобы строки (как данные, так и ключ) были преобразованы в одинаковую последовательность байтов в обеих системах (1С и PHP).

  1. Кодировка в 1С: По умолчанию строки в 1С могут внутренне обрабатываться как UTF-16. Однако, при взаимодействии с внешними системами, особенно веб-сервисами и PHP, стандартной является кодировка UTF-8. Если не указать кодировку явно при преобразовании в двоичные данные, результат может быть непредсказуемым. Используйте КодировкаТекста.UTF8 при вызове ПолучитьДвоичныеДанныеИзСтроки(), как показано в примере.
  2. Кодировка в PHP: В PHP строки рассматриваются как последовательность байтов. Функция hash_hmac() работает с этими байтами напрямую. Если данные или ключ были получены из внешнего источника с другой кодировкой (например, Windows-1251), их необходимо сначала привести к UTF-8 (или той кодировке, которая используется в 1С).

Важно убедиться, что как СтрокаСДанными, так и СекретныйКлюч перед преобразованием в двоичные данные в 1С, и до передачи в hash_hmac в PHP, представляют собой одну и ту же последовательность байтов (например, в UTF-8).

Проверка подписи в PHP

Для проверки подписи в PHP используется функция hash_hmac(). Рассмотрим, как она должна быть вызвана для совместимости с нашим решением на 1С.


// $api_key - ваш секретный ключ в виде строки,
// который должен быть в той же кодировке, что и в 1С (например, UTF-8)
$api_key = "МойСуперСекретныйКлючДляHMAC"; 

// $string_to_sign - данные, которые нужно подписать,
// также в той же кодировке (например, UTF-8)
$string_to_sign = "Это данные, которые нужно подписать.";

// Расчет HMAC-SHA256 в PHP
// Параметр 'sha256' указывает алгоритм хеширования
// $string_to_sign - данные
// $api_key - секретный ключ
// true - указывает, что результат должен быть необработанными двоичными данными (RAW binary output)
$hmac_raw_binary = hash_hmac('sha256', $string_to_sign, $api_key, true);

// Если в 1С мы получали Base64, то в PHP тоже преобразуем raw binary в Base64
$hmac_base64 = base64_encode($hmac_raw_binary);

// Если в 1С мы получали Hex, то в PHP тоже преобразуем raw binary в Hex
$hmac_hex = bin2hex($hmac_raw_binary);

echo "HMAC (Base64): " . $hmac_base64 . "\n";
echo "HMAC (Hex): " . $hmac_hex . "\n";

Обратите внимание на параметр true в hash_hmac('sha256', ..., true). Он указывает PHP вернуть необработанные двоичные данные подписи. Это позволяет нам затем преобразовать их либо в Base64 (с помощью base64_encode()) для сравнения с ПодписьBase64 из 1С, либо в шестнадцатеричную строку (с помощью bin2hex()) для сравнения с ПодписьДляPHP из 1С.

Ключевые выводы и рекомендации

  1. Используйте специализированные функции: Для HMAC-SHA256 в 1С используйте встроенные функции, такие как РаботаВМоделиСервисаБТС.Подпись. Не пытайтесь реализовать HMAC вручную через простую конкатенацию и хеширование.
  2. Согласованность кодировок: Это самый критичный аспект. Убедитесь, что все строки (данные и ключ) преобразованы в двоичные данные с одной и той же кодировкой (например, UTF-8) как в 1С, так и в PHP. Метод ПолучитьДвоичныеДанныеИзСтроки(Строка, КодировкаТекста.UTF8) в 1С и правильная обработка строк в PHP (например, с помощью mb_convert_encoding(), если исходная кодировка неизвестна) являются залогом успеха.
  3. Единый формат вывода: Договоритесь о формате вывода подписи (Base64 или Hex). Если 1С возвращает Base64, а PHP по умолчанию выдает Hex, выполните соответствующее преобразование на одной из сторон для корректного сравнения. В PHP удобно получить необработанные двоичные данные с помощью hash_hmac(..., true), а затем уже кодировать их в нужный формат (base64_encode или bin2hex).
  4. Безопасность ключа: Секретный ключ должен быть надежным, случайным и не должен храниться в открытом виде или передаваться по незащищенным каналам.

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

← На главную