Подпись оповещений (RSA)
В этом разделе описан алгоритм подписи и проверки оповещений (callback-сообщений), использующий RSA-ключи.
Назначение подписи
Подпись оповещений решает следующие задачи:
-
подтверждает, что сообщение отправлено платформой HighHelp;
-
гарантирует, что данные в теле оповещения не были изменены по пути.
| По умолчанию для подписи оповещений используется асимметричный алгоритм RSA-SHA256. В качестве альтернативного метода доступен симметричный алгоритм HMAC-SHA512 (см. раздел Подпись оповещений (HMAC)). |
Перед валидацией подписи выполните стандартные проверки (формат JSON, наличие обязательных полей и т.п.).
|
Публичный RSA-ключ отображается в личном кабинете только если для кассы настроен алгоритм RSA-SHA256. Текущий алгоритм подписи отображается в разделе API → Настройки Callback (например, Алгоритм: RSA или Алгоритм: HMAC). Если для кассы настроен алгоритм HMAC, используйте раздел Подпись оповещений (HMAC). |
Получение публичного ключа для проверки подписи
Для проверки подписи оповещений используйте публичный RSA-ключ, настроенный для кассы.
Порядок получения ключа:
-
Откройте личный кабинет мерчанта.
-
Перейдите в раздел API → Настройки Callback.
-
В модальном окне найдите блок:
-
Public Key — если для кассы настроен алгоритм RSA.
-
HMAC key — если для кассы настроен алгоритм HMAC (в этом случае используйте раздел Подпись оповещений (HMAC)).;
-
-
Для RSA нажмите на иконку скачивания в блоке Public key и сохраните файл ключа.
-
Настройте сервис обработки оповещений на использование этого ключа в алгоритме проверки подписи.
Нормализация тела запроса
Нормализация выполняется рекурсивным обходом JSON-структуры и формированием списка строк путь:значение с последующей сортировкой.
Алгоритм:
-
Обойдите JSON-структуру рекурсивно.
-
Для каждого значения сформируйте сформируйте путь в виде
ключ1:ключ2:…:значение. -
Для массивов используйте индексы элементов:
:0,:1, … -
Для булевых значений используйте:
true→1,false→0. -
Отсортируйте все строки по алфавиту.
-
Соедините строки через
;.
Пример исходных данных:
{
"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)
Требования к нормализации
При реализации алгоритма нормализации учитывайте следующие требования:
-
Булевы значения: преобразуются в целочисленное представление (
true→1,false→0). -
Значения null: преобразуются в строку
None. Не используйте пустые строки или пробелы. -
Числа: используйте стандартное строковое представление без локализации (разделителей групп разрядов, локальных форматов). Не добавляйте незначащие нули.
-
Массивы: порядок элементов сохраняется в исходной последовательности. Индексы элементов добавляются к пути как
:0,:1,:2, … -
Объекты: после формирования всех пар
путь:значениевыполняется сортировка по алфавиту по полной строке. -
Кодировка символов: используйте UTF-8 для кодирования перед применением Base64Url. Не изменяйте регистр символов.
-
Пробелы и форматирование: не добавляйте и не удаляйте пробелы в значениях. Используйте точные значения из JSON-структуры.
Алгоритм формирования подписи
Подпись формируется на стороне HighHelp по следующему алгоритму:
-
Нормализация JSON-тела оповещения функцией
normalize_message. -
Кодирование нормализованной строки в Base64Url.
-
Конкатенация полученной строки и
timestamp(строка). -
Вычисление хеша SHA-256 от результирующей строки.
-
Подписание хеша приватным RSA-ключом по схеме RSA-SHA256.
-
Кодирование подписи в Base64Url.
-
Передача подписи и метки времени в HTTP-заголовках оповещения.
На стороне мерчанта необходимо воспроизвести шаги 1-6 и проверить подпись с использованием публичного ключа кассы.
Проверка подписи на стороне мерчанта
Для проверки подписи выполните следующие шаги:
-
Получите JSON-тело оповещения и значения заголовков с:
-
меткой времени;
-
подписью;
-
публичным RSA-ключом кассы (если он передается в заголовке), либо заранее сохраните публичный ключ из личного кабинета.
-
-
Нормализуйте тело оповещения в строку
normalizedпо описанному алгоритму. -
Закодируйте
normalizedв Base64Url. -
Сконструируйте строку
message = encoded + str(timestamp). -
Вычислите SHA-256 от
message. -
Декодируйте подпись из Base64Url.
-
Проверьте подпись с использованием публичного RSA-ключа.
Пример проверки подписи (Python3)
import base64
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature.pkcs1_15 import PKCS115_SigScheme
def verify_rsa_callback_signature(
payload: dict,
signature_b64url: str,
public_key_pem: bytes,
timestamp: int,
) -> bool:
"""
Проверить подпись оповещения по алгоритму RSA-SHA256.
"""
try:
# Нормализация и подготовка сообщения
normalized = normalize_message(payload)
normalized_bytes = normalized.encode("utf-8")
encoded_base64url = base64.urlsafe_b64encode(normalized_bytes).decode("utf-8")
concatenated_with_ts = f"{encoded_base64url}{timestamp}"
message = concatenated_with_ts.encode("utf-8")
# Подготовка ключа и подписи
public_key = RSA.import_key(public_key_pem)
verifier = PKCS115_SigScheme(public_key)
signature = base64.urlsafe_b64decode(signature_b64url)
# Проверка подписи
verifier.verify(SHA256.new(message), signature)
return True
except Exception:
return False
Рекомендации по безопасности
-
Сохраните публичный ключ кассы в конфигурации приложения.
-
Обновляйте публичный ключ при изменении ключей в личном кабинете.
-
Проверяйте идемпотентность оповещений по полям
project_id,payment_id,status,sub_status.Проверка индемпотентности гаррантирует, что повторная обработка одного и того же оповещения не изменяет конечный результат. Сохраняйте комбинацию этих полей для предотвращения дублирования операций. -
Проверяйте допустимое окно времени для
timestamp.