Аутентификация и подпись запросов (RSA)
В этом разделе описана аутентификация запросов к HighHelp API с использованием алгоритма RSA-SHA256 и формат HTTP-заголовков, которые необходимо передавать в каждом запросе.
Используется асимметричная криптография: приватный ключ хранится в вашей интеграции и применяется для подписи запросов, публичный ключ хранится на стороне HighHelp и используется для проверки подписи.
RSA-SHA256 является алгоритмом подписи по умолчанию для всех новых касс. При создании кассы автоматически генерируется пара RSA-ключей.
Назначение подписи
Подпись запроса используется для:
-
аутентификации запроса (подтверждение владения ключом);
-
контроля целостности JSON-тела запроса.
Регистрация в системе
Регистрацию выполняйте через личный кабинет мерчанта. Доступ в личный кабинет предоставляет специалист HighHelp.
-
Создайте кассу во вкладке Кассы.
-
Обратитесь к прикреплённому специалисту HighHelp для настройки кассы и проведения верификации.
Описание формата подписи оповещений и алгоритма проверки приведено в разделе Подпись оповещений (RSA).
Получение API-ключей
Чтобы получить RSA-ключи для подписи запросов:
-
После регистрации в системе перейдите во вкладку API.
-
Найдите нужную кассу и нажмите на иконку шестерёнки.
-
В открывшемся окне нажмите кнопку Обновить API ключи.
-
В окне API ключи проверьте информацию о кассе.
-
Нажмите и удерживайте кнопку Сгенерировать ключ до завершения генерации.
-
Скачайте и сохраните:
-
private_key_<id>.pem— приватный RSA-ключ для подписи запросов; -
public_key_<id>.pem— публичный RSA-ключ для подписи запросов; -
hmac_private_key_<id>.txt— HMAC-ключ для подписи запросов (используется при HMAC-SHA512).
-
|
Приватный RSA-ключ генерируется на стороне браузера и не сохраняется на стороне HighHelp. На стороне HighHelp сохраняется только публичный RSA-ключ кассы, который используется для проверки подписи запросов. |
Обновление API-ключей
Чтобы обновить API-ключи кассы:
-
Откройте личный кабинет мерчанта.
-
Перейдите во вкладку API.
-
Найдите нужную кассу и нажмите на иконку шестеренки.
-
В открывшемся окне нажмите кнопку Обновить API ключи.
-
В модальном окне API ключи проверьте информацию о кассе.
-
Нажмите и удерживайте кнопку Сгенерировать ключ до завершения генерации.
-
Сохраните результат генерации:
-
ID— идентификатор кассы (UUID). Используется при взаимодействии с API и передаётся в заголовкеx-access-merchant-id; -
private_key_<id>.pem— новый приватный RSA-ключ для подписи запросов; -
public_key_<id>.pem— новый публичный RSA-ключ для подписи запросов; -
hmac_private_key_<id>.txt— новый HMAC-ключ для подписи запросов.
-
-
Обновите конфигурацию вашей интеграции, заменив старые ключи на новые.
|
После обновления API-ключей старые ключи перестанут работать немедленно. Убедитесь, что новые ключи корректно настроены в вашей интеграции до начала использования. Рекомендуется выполнять обновление в период минимальной нагрузки на систему. |
|
Кнопка Обновить API ключи обновляет только ключи для подписи запросов и не влияет на ключи для подписи оповещений. |
Аутентификация в API
Для аутентификации запросов API и проверки целостности тела запроса используйте следующие HTTP-заголовки:
-
x-access-timestamp -
x-access-merchant-id -
x-access-merchant-algorithm(необязательный для RSA) -
x-access-signature -
x-access-token
Заголовок x-access-timestamp
x-access-timestamp содержит время формирования запроса.
-
Формат — Unix timestamp в секундах (количество секунд с 01.01.1970 00:00:00 UTC).
-
Тип — строка с десятичным целым числом.
Пример:
x-access-timestamp: 1716299720
Заголовок x-access-merchant-id
x-access-merchant-id содержит идентификатор кассы.
-
Значение —
ID(UUID), полученный при генерации ключей для кассы. -
Тип — строка.
В примерах кода идентификатор кассы передается через переменную project_id.
Пример:
x-access-merchant-id: 57aff4db-b45d-42bf-bc5f-b7a499a01782
Заголовок x-access-merchant-algorithm
x-access-merchant-algorithm позволяет явно указать используемый алгоритм подписи.
-
Для RSA заголовок является необязательным.
-
Возможное значение для RSA:
RSA-SHA256(значение согласуйте со специалистом HighHelp).
Заголовок x-access-token
x-access-token содержит публичный ключ кассы, закодированный в формате Base64Url.
Порядок формирования значения x-access-token:
-
Загрузите приватный RSA-ключ в формате PEM.
-
Получите из него публичный ключ.
-
Экспортируйте публичный ключ в бинарном виде.
-
Закодируйте полученные байты в Base64Url.
-
Передавайте полученную строку в заголовке
x-access-token.
Пример расчета значения x-access-token на Python (библиотека cryptography):
from cryptography.hazmat.primitives import serialization
import base64
# private_key: объект приватного ключа, загруженный из PEM
public_key_bytes = private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
api_key = base64.urlsafe_b64encode(public_key_bytes).decode("utf-8")
# api_key используйте как значение заголовка x-access-token
Заголовок x-access-signature
x-access-signature содержит цифровую подпись тела запроса и метки времени.
Алгоритм подписи (схематично):
message = base64url(normalized_payload) + str(timestamp)
signature = RSA-SHA256(message)
x-access-signature = base64url(signature)
Где:
-
normalized_payload— нормализованное представление JSON-тела запроса; -
timestamp— значение заголовкаx-access-timestamp(Unix timestamp в секундах).
Если тело запроса отсутствует, используйте пустой объект {}.
Нормализация тела запроса
Для формирования подписи используется нормализованное представление JSON-тела запроса.
Алгоритм нормализации:
-
Обойдите JSON-структуру рекурсивно.
-
Для каждого значения сформируйте путь в виде
ключ1:ключ2:…:значение. -
Для массивов используйте индексы элементов:
:0,:1, … -
Для булевых значений используйте:
true→1,false→0. -
Отсортируйте все строки по алфавиту.
-
Соедините строки через
;.
Пример реализации нормализации (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)
if obj is None:
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: преобразуются в пустую строку
"". -
Числа: используйте стандартное строковое представление без локализации (разделителей групп разрядов, локальных форматов). Не добавляйте незначащие нули.
-
Массивы: порядок элементов сохраняется в исходной последовательности. Индексы элементов добавляются к пути как
:0,:1,:2, … -
Объекты: после формирования всех пар
путь:значениевыполняется сортировка по алфавиту по полной строке. -
Кодировка символов: используйте UTF-8 для кодирования перед применением Base64Url. Не изменяйте регистр символов.
-
Base64Url: кодируйте строку в формате Base64Url (RFC 4648): замените
+на-,/на_; символы выравнивания=не удаляйте. -
Пробелы и форматирование: не добавляйте и не удаляйте пробелы в значениях. Используйте точные значения из JSON-структуры.
Алгоритм формирования подписи
-
Сформируйте объект
payloadс телом запроса. -
Нормализуйте
payloadфункциейnormalize_message():joined_result = normalize_message(payload) -
Закодируйте нормализованную строку в Base64Url и добавьте метку времени:
timestamp = int(time.time()) message = "{}{}".format( base64.urlsafe_b64encode(joined_result.encode()).decode("utf-8"), str(timestamp), ).encode('utf-8') -
Подпишите байтовую строку
messageприватным RSA-ключом по алгоритму RSA-SHA256 (PKCS#1 v1.5). -
Закодируйте байты подписи в Base64Url и передайте результат в заголовке
x-access-signature.
Пример запроса с RSA-подписью (Python3)
import base64
import json
import time
import requests
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
url = "https://api.hh-processing.com/api/v1/payment/p2p/payin"
# Идентификатор кассы (UUID)
project_id = "57aff4db-b45d-42bf-bc5f-b7a499a01782"
# Приватный RSA-ключ (PEM)
with open("private_key.pem", "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
)
payload = {
"general": {
"project_id": project_id
}
}
def parse_json(prefix, obj, result):
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)
if obj is None:
obj = ""
result.append(f"{prefix}:{obj}")
def normalize_message(data):
result = []
parse_json("", data, result)
result.sort()
return ";".join(result)
# Публичный ключ (Base64Url)
public_key_bytes = private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
api_key = base64.urlsafe_b64encode(public_key_bytes).decode("utf-8")
# Метка времени Unix (секунды)
timestamp = int(time.time())
# Нормализация тела запроса
joined_result = normalize_message(payload)
# Формирование сообщения для подписи
message = "{}{}".format(
base64.urlsafe_b64encode(joined_result.encode()).decode("utf-8"),
str(timestamp),
).encode("utf-8")
# Подпись RSA-SHA256
signature_bytes = private_key.sign(
message,
padding.PKCS1v15(),
hashes.SHA256()
)
signature_b64url = base64.urlsafe_b64encode(signature_bytes).decode("utf-8")
# Заголовки
headers = {
"content-type": "application/json",
"x-access-token": api_key,
"x-access-signature": signature_b64url,
"x-access-merchant-id": project_id,
"x-access-timestamp": str(timestamp),
}
# Тело запроса (JSON)
dumped = json.dumps(payload, separators=(",", ":")) if payload else "{}"
response = requests.post(url, headers=headers, data=dumped)
print(response.status_code)
Рекомендации по безопасности
-
Храните приватный RSA-ключ на стороне сервера.
-
Не логируйте приватный ключ.
-
Обновляйте ключи при смене кассы или по запросу через специалиста HighHelp.
-
Публичный ключ используйте для проверки подписи.
Форма для проверки подписи
Используйте форму ниже для проверки корректности формирования подписи RSA-SHA256 для запросов к API HighHelp.
|
Обработка введенных данных выполняется локально в браузере; данные не передаются на сервер. |
Выполнение проверки
-
Вставьте JSON-тело запроса в первое поле.
-
Вставьте приватный RSA-ключ (PEM).
-
Укажите временную метку в формате Unix timestamp.
-
Вставьте подпись
x-access-signature, которую необходимо проверить. -
Нажмите кнопку Проверить подпись.
Форма отобразит пошаговый процесс формирования подписи и вычисленную подпись. Если введенная подпись не совпадает, используйте вычисленное значение как корректное.
Пример тестовых данных
Для проверки подписи можно использовать следующие тестовые данные:
JSON-тело запроса:
{"general":{"project_id":"test-project-123"},"payment":{"amount":100000,"currency":"USD"}}
Приватный RSA-ключ (PEM):
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
Timestamp: 1716299720
Полученная подпись: signature-to-verify
После нажатия на кнопку Проверить подпись форма отобразит:
-
Нормализованное представление данных.
-
base64url(normalized). -
message = base64url(normalized) + timestamp. -
Вычисленную подпись (
x-access-signature). -
Результат сравнения подписи.