Getting Started
Ключи для Payin
Как сгенерировать ed25519-ключ и подписать тело запроса. Используется в Payin Classic и Payin H2H.
Ключи для Payin
Эта схема подписи применяется только к Payin Classic и Payin H2H. Для Payout используется другая схема — см. Ключи для Payout.
Все запросы и ответы Payin подписываются по алгоритму ed25519 (RFC 8032). Библиотеки для ed25519 есть практически во всех языках.
Примеры на этой странице используют имена заголовков Payin Classic —
Key-Id и Signature. В Payin H2H те же значения передаются в заголовках X-KeyId и X-Signature. Алгоритм подписи, формат ключей и порядок проверки полностью идентичны — отличается только название заголовков.Как считается подпись
- Для HTTP POST — подпись вычисляется из тела запроса (
request body). - Для WebSocket — подпись вычисляется от значения поля
bodyсообщения.
Результат кодируется в Base64 и передаётся:
- в HTTP — в заголовке
Signature(Payin Classic) илиX-Signature(Payin H2H); - в WebSocket — в поле
Signatureсообщения.
Вместе с подписью передаётся идентификатор публичного ключа: Key-Id (Payin Classic) или X-KeyId (Payin H2H). Значение фиксированное и выдаётся вручную до начала работы.
Разные пары ключей для запросов и ответов
Ответы сервера тоже подписаны — но другой парой ключей. Не путайте направления.
| Направление | Приватный ключ | Публичный ключ |
|---|---|---|
| Клиент → сервер (ваши запросы) | у клиента | у сервера (вы передаёте его нам вручную) |
| Сервер → клиент (наши ответы) | у сервера | у клиента (мы передаём его вам вручную) |
Все ключи и подписи ходят в Base64.
Генерация ключей
<?php
$sign_seed = random_bytes(SODIUM_CRYPTO_SIGN_SEEDBYTES);
$sign_pair = sodium_crypto_sign_seed_keypair($sign_seed);
$sign_secret = sodium_crypto_sign_secretkey($sign_pair);
$sign_public = sodium_crypto_sign_publickey($sign_pair);
file_put_contents('./private.key', $sign_secret);
$sign_public_base64 = base64_encode($sign_public);
file_put_contents('./public.key', $sign_public_base64);
import (
"crypto/ed25519"
"encoding/base64"
)
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
// обработка ошибки
}
pubBase64 := base64.StdEncoding.EncodeToString(pub)
// pubBase64 передаётся нам вручную до начала работы
import { generateKeyPairSync } from 'node:crypto'
const { privateKey, publicKey } = generateKeyPairSync('ed25519')
const publicBase64 = publicKey
.export({ format: 'der', type: 'spki' })
.slice(-32)
.toString('base64')
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
import base64
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
public_raw = public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
public_b64 = base64.b64encode(public_raw).decode()
Не используйте
openssl для генерации ed25519-ключей — утилита добавляет в файл служебные байты, которые сломают подпись.Формирование подписи
$sign_secret = file_get_contents('./private.key', true);
$message = '{"Id":"1643731972","Kind":"BankCard","Currency":"RUB","Sum100":10000}';
$signature = sodium_crypto_sign_detached($message, $sign_secret);
$signature_base64 = base64_encode($signature);
// $signature_base64 -> заголовок "Signature"
// $message -> тело HTTP-запроса
import (
"crypto"
"crypto/ed25519"
"encoding/base64"
"os"
)
privB, err := os.ReadFile("private.key")
if err != nil || len(privB) != ed25519.PrivateKeySize {
// обработка ошибки
}
privK := ed25519.PrivateKey(privB)
msg := `{"Id":"1643731972","Kind":"BankCard","Currency":"RUB","Sum100":10000}`
sign, err := privK.Sign(nil, []byte(msg), crypto.Hash(0))
if err != nil {
// обработка ошибки
}
signatureBase64 := base64.StdEncoding.EncodeToString(sign)
// signatureBase64 -> заголовок "Signature"
// msg -> тело HTTP-запроса
import { createPrivateKey, sign } from 'node:crypto'
import { readFileSync } from 'node:fs'
const privateKey = createPrivateKey({
key: readFileSync('./private.key'),
format: 'der',
type: 'pkcs8',
})
const message = JSON.stringify({ Id: '1643731972', Kind: 'BankCard', Currency: 'RUB', Sum100: 10000 })
const signature = sign(null, Buffer.from(message), privateKey).toString('base64')
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
import base64
with open('./private.key', 'rb') as f:
private_key = serialization.load_pem_private_key(f.read(), password=None)
message = b'{"Id":"1643731972","Kind":"BankCard","Currency":"RUB","Sum100":10000}'
signature = private_key.sign(message)
signature_b64 = base64.b64encode(signature).decode()
Проверка ответа
Сервер присылает заголовки Key-Id и Signature (в Payin H2H — X-KeyId и X-Signature). Декодируйте подпись из Base64 и проверьте её для тела ответа нашим публичным ключом (который вы получили вручную).