Подпись оповещений (HMAC)

В этом разделе описан алгоритм подписи и проверки оповещений (callback-сообщений), использующий HMAC-ключ.

Назначение подписи

Подпись оповещений с HMAC решает следующие задачи:

  • подтверждает, что сообщение отправлено платформой HighHelp, владеющей секретным ключом;

  • гарантирует, что данные в теле оповещения не были изменены по пути.

По умолчанию для подписи оповещений используется асимметричный алгоритм RSA-SHA256 (см. раздел Подпись оповещений (RSA)). HMAC-SHA512 доступен в качестве альтернативного метода.

Для подписи используется симметричный алгоритм HMAC-SHA512 и общий секретный ключ, известный только вашей системе и HighHelp.

При создании ключей кассы формируются HMAC-ключ и пара RSA-ключей.

Секретный HMAC-ключ доступен для скачивания только в момент генерации или обновления. После скачивания в разделе APIНастройки Callback отображается маскированное значение ключа; повторное скачивание недоступно, доступно только обновление ключа.

Смена алгоритма подписи с RSA на HMAC

Перед обращением к специалисту убедитесь, что интеграция на стороне мерчанта готова к работе с HMAC-подписью. После переключения алгоритма в личном кабинете будет доступен только HMAC-ключ, а все запросы и оповещения должны использовать HMAC-SHA512.

Для переключения алгоритма подписи с RSA на HMAC обратитесь к специалисту HighHelp. Смена алгоритма выполняется на стороне HighHelp после согласования.

Перед переключением на HMAC убедитесь, что:

  • HMAC-ключ для кассы сгенерирован;

  • интеграция на стороне мерчанта готова к работе с HMAC-подписью;

  • обновлены скрипты для формирования подписи запросов и проверки подписи оповещений.

После смены алгоритма:

  • в личном кабинете доступен только ключ активного алгоритма подписи: Public Key для RSA или HMAC Key для HMAC;

  • в настройках оповещений отображается текущий алгоритм — HMAC.

Получение HMAC-ключа

Для проверки подписи оповещений используйте секретный HMAC-ключ, настроенный для кассы. Тот же ключ используется для формирования подписи запросов к API.

Секретный HMAC-ключ доступен для скачивания только в момент генерации или обновления. После скачивания в разделе APIНастройки Callback отображается маскированное значение ключа; повторное скачивание недоступно, доступно только обновление ключа.

Генерация и обновление HMAC-ключа

При генерации ключей для кассы одновременно создаются HMAC-ключ и пара RSA-ключей. Если требуется сгенерировать HMAC-ключ отдельно или обновить существующий ключ, используйте инструкции ниже.

Секретный HMAC-ключ доступен для скачивания только в момент генерации или обновления. После скачивания в разделе APIНастройки Callback отображается маскированное значение ключа; повторное скачивание недоступно, доступно только обновление ключа.

Генерация HMAC-ключа

Если для кассы не был сгенерирован HMAC-ключ при первоначальной настройке, выполните следующие действия:

  1. Откройте личный кабинет мерчанта.

  2. Перейдите в раздел APIНастройки Callback.

  3. Нажмите на иконку + в блоке HMAC key.

  4. Сохраните ключ в защищенном хранилище и передайте команде разработки.

Обновление HMAC-ключа

Если для кассы настроен алгоритм HMAC-SHA512 и требуется обновить только HMAC-ключ без обновления RSA-ключей, выполните следующие действия:

  1. Откройте личный кабинет мерчанта.

  2. Перейдите в раздел APIНастройки Callback.

  3. Убедитесь, что текущий алгоритм подписи — HMAC (отображается внизу модального окна: Алгоритм: HMAC).

  4. Нажмите на кнопку Обновить ключ в блоке HMAC key.

  5. В открывшемся окне подтвердите операцию.

  6. Сохраните новый секретный HMAC-ключ.

  7. Обновите конфигурацию интеграции на стороне мерчанта, заменив старый HMAC-ключ на новый.

После обновления HMAC-ключа старый ключ перестанет работать немедленно. Убедитесь, что новый ключ корректно настроен на стороне мерчанта до начала использования. Рекомендуется выполнять обновление в период минимальной нагрузки на систему.

Нормализация тела запроса

Для формирования подписи используется нормализованное представление JSON-тела запроса.

Алгоритм нормализации:

  1. Обойдите JSON-структуру рекурсивно.

  2. Для каждого значения сформируйте сформируйте путь в виде ключ1:ключ2:…​:значение.

  3. Для массивов используйте индексы элементов: :0, :1, …​

  4. Для булевых значений используйте: true1, false0.

  5. Отсортируйте все строки по алфавиту.

  6. Соедините строки через ;.

Пример исходных данных:

{
  "amount": 100,
  "status": "success",
  "is_paid": true,
  "data": {
    "id": 123,
    "is_active": false
  }
}

Результат нормализации:

amount:100;data:id:123;data:is_active:0;is_paid:1;status:success

Пример реализации нормализации (Python3)

def parse_json(prefix, obj, result):
    """
    Рекурсивный обход JSON-структуры для формирования пар путь:значение.
    """
    if isinstance(obj, dict):
        for key, value in obj.items():
            if isinstance(key, bool):
                key = int(key)
            new_prefix = f"{prefix}:{key}" if prefix else str(key)
            parse_json(new_prefix, value, result)
    elif isinstance(obj, list):
        for index, item in enumerate(obj):
            if isinstance(item, bool):
                item = int(item)
            new_prefix = f"{prefix}:{index}"
            parse_json(new_prefix, item, result)
    else:
        if isinstance(obj, bool):
            obj = int(obj)
        result.append(f"{prefix}:{obj}")


def normalize_message(payload: dict) -> str:
    """
    Нормализация JSON в детерминированную строку (формат: путь:значение через ;).
    """
    items: list[str] = []
    parse_json("", payload, items)
    items.sort()
    return ";".join(items)

Требования к нормализации

При реализации алгоритма нормализации учитывайте следующие требования:

  • Булевы значения: преобразуются в целочисленное представление (true1, false0).

  • Значения null: преобразуются в строку None. Не используйте пустые строки или пробелы.

  • Числа: используйте стандартное строковое представление без локализации (разделителей групп разрядов, локальных форматов). Не добавляйте незначащие нули.

  • Массивы: порядок элементов сохраняется в исходной последовательности. Индексы элементов добавляются к пути как :0, :1, :2, …​

  • Объекты: после формирования всех пар путь:значение выполняется сортировка по алфавиту по полной строке.

  • Кодировка символов: используйте UTF-8 для кодирования перед применением Base64Url. Не изменяйте регистр символов.

  • Пробелы и форматирование: не добавляйте и не удаляйте пробелы в значениях. Используйте точные значения из JSON-структуры.

Алгоритм формирования подписи

Подпись формируется на стороне HighHelp по следующему алгоритму:

  1. Нормализация JSON-тела оповещения функцией normalize_message.

  2. Кодирование нормализованной строки в Base64Url.

  3. Конкатенация полученной строки и timestamp (строка).

  4. Вычисление HMAC-SHA512 от результирующей строки с использованием секретного ключа.

  5. Кодирование значения HMAC в Base64Url.

  6. Передача подписи и метки времени в HTTP-заголовках оповещения.

На стороне мерчанта необходимо воспроизвести шаги 1-5 и сравнить вычисленную подпись с полученной.

Проверка подписи на стороне мерчанта

Для проверки подписи:

  1. Получите JSON-тело оповещения и значения заголовков с:

    • меткой времени;

    • подписью;

    • идентификатором кассы (для выбора корректного HMAC-ключа).

  2. Найдите соответствующий секретный ключ для кассы.

  3. Нормализуйте тело оповещения в строку normalized по описанному алгоритму.

  4. Закодируйте normalized в Base64Url.

  5. Сконструируйте строку message = encoded + str(timestamp).

  6. Вычислите HMAC-SHA512 от message с использованием секретного ключа.

  7. Закодируйте результат в Base64Url и сравните с подписью из оповещения.

Пример проверки подписи (Python3)

import base64
import hmac
import hashlib


def verify_hmac_callback_signature(
    payload: dict,
    signature_b64url: str,
    secret_key: bytes,
    timestamp: int,
) -> bool:
    """
    Проверить подпись оповещения по алгоритму HMAC-SHA512.
    """
    try:
        # Нормализация и подготовка сообщения
        normalized = normalize_message(payload)
        encoded = base64.urlsafe_b64encode(normalized.encode("utf-8")).decode("utf-8")
        message = f"{encoded}{timestamp}".encode("utf-8")

        # Вычисление ожидаемой подписи
        expected = hmac.new(secret_key, message, hashlib.sha512).digest()
        expected_b64url = base64.urlsafe_b64encode(expected).decode("utf-8")

        # Сравнение подписи, устойчивое к тайминговым атакам
        return hmac.compare_digest(expected_b64url, signature_b64url)
    except Exception:
        return False

Рекомендации по безопасности

  • Храните секретный HMAC-ключ на стороне сервера.

  • Обновляйте ключи по запросу через специалиста HighHelp.

  • Не передавайте ключи по незащищенным каналам.

  • Не логируйте ключ полностью. Маскируйте значение: первые 3 символа + 7 астерисков (*) + последние 3 символа.

    Пример маскирования ключа
    def masked_hmac_key(key: str) -> str:
        return key[:3] + "*******" + key[-3:]
  • Проверяйте идемпотентность оповещений по полям project_id, payment_id, status, sub_status.

    Проверка индемпотентности гаррантирует, что повторная обработка одного и того же оповещения не изменяет конечный результат. Сохраняйте комбинацию этих полей для предотвращения дублирования операций.
  • Проверяйте допустимое окно времени для timestamp.

Форма для проверки подписи

Используйте форму ниже для проверки корректности формирования подписи HMAC-SHA512 для запросов к API HighHelp.

Обработка введенных данных выполняется локально в браузере; данные не передаются на сервер.

Выполнение проверки

  1. Вставьте JSON-тело запроса в первое поле.

  2. Введите ваш секретный ключ.

  3. Укажите временную метку в формате Unix timestamp.

  4. Вставьте подпись, которую необходимо проверить.

  5. Нажмите кнопку Проверить подпись.

Форма отобразит пошаговый процесс формирования подписи и результат проверки предоставленной подписи.

Пример тестовых данных

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

JSON-тело запроса:

{"general":{"project_id":"test-project-123"},"payment":{"amount":100000,"currency":"USD"}}

Секретный ключ: test-secret-key

Timestamp: 1716299720

После нажатия на кнопку Проверить подпись форма отобразит:

  1. Нормализованное представление данных.

  2. Используемую временную метку.

  3. Вычисленную подпись.

  4. Результат проверки предоставленной подписи.