Перейти к содержанию

Публичный ключ для криптограммы

Возвращает актуальный публичный RSA-ключ KVELL. Этим ключом на стороне мерчанта шифруются реквизиты банковской карты перед отправкой в KVELL: вместо открытых реквизитов передаётся криптограмма.

Сценарий интеграции

  1. Запросите публичный ключ этим методом.
  2. Сохраните public_key и key_id в своём кэше до момента refresh_after.
  3. Зашифруйте реквизиты карты полученным ключом и получите криптограмму.
  4. Передайте криптограмму в API-метод, который её принимает.
  5. Перед следующим использованием ключа проверьте refresh_after и при необходимости запросите ключ заново.

URL

GET https://api.pay.kvell.group/v1/cryptogram/public-key
GET https://api.pay.stage.kvell.group/v1/cryptogram/public-key

Запрос

Заголовки

Название Тип Обязательно Описание
X-Api-Key string Да Идентификатор магазина.

Пример

curl --request GET \
  --url 'https://api.pay.stage.kvell.group/v1/cryptogram/public-key' \
  --header 'X-Api-Key: 00000000-0000-4000-8000-000000000000'

Ответ

Выберите HTTP-код, чтобы посмотреть пример, параметры ответа и рекомендуемые действия.

Пример ответа 200 (OK)
{
  "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6tJntpB49kze14gfIkQ\nAQIDAQAB\n-----END PUBLIC KEY-----\n",
  "key_id": "068eb60b259651c4",
  "refresh_after": "2026-08-20T10:20:48.650203Z"
}

Параметры ответа

Параметр Тип Описание
public_key string Публичный ключ RSA-2048 в формате PEM.
key_id string Идентификатор ключа. Вычисляется как SHA-256 от DER-представления SubjectPublicKeyInfo, усечённая до первых 16 символов, поэтому его можно пересчитать самостоятельно из public_key.
refresh_after string Момент в UTC по ISO 8601, до которого возвращённый ключ гарантированно актуален. Подробнее — в разделе «Кэширование и ротация ключа».

Что делать дальше

  1. Сохраните public_key и key_id в кэше до refresh_after.
  2. Используйте ключ для формирования криптограммы.
Пример ответа 403 (Forbidden)
{
  "errors": [
    {
      "code": 20037,
      "message": "Доступ запрещен"
    }
  ]
}

Параметры ответа

Параметр Тип Описание
errors array Список ошибок. Коды и рекомендации приведены в разделе «Ошибки HTTP-ответов».
errors[].code integer Код ошибки. В примере — 20037.
errors[].message string Описание причины запрета доступа.
Пример ответа 404 (Not Found)
{
  "errors": [
    {
      "code": 20006,
      "message": "Магазин не найден"
    }
  ]
}

Параметры ответа

Параметр Тип Описание
errors array Список ошибок. Коды и рекомендации приведены в разделе «Ошибки HTTP-ответов».
errors[].code integer Код ошибки. В примере — 20006.
errors[].message string Описание ресурса, который не найден.
Пример ответа 422 (Unprocessable Entity)
{
  "errors": [
    {
      "code": 20098,
      "message": "x-api-key: Field required"
    }
  ]
}

Параметры ответа

Параметр Тип Описание
errors array Список ошибок валидации. Коды и рекомендации приведены в разделе «Ошибки HTTP-ответов».
errors[].code integer Код ошибки. Для ошибки поля — 20098.
errors[].message string Поле и причина ошибки валидации.
Пример ответа 503 (Service Unavailable)
{
  "errors": [
    {
      "code": 20043,
      "message": "Публичный ключ криптограммы временно недоступен"
    }
  ]
}

Параметры ответа

Параметр Тип Описание
errors array Список ошибок. Коды и рекомендации приведены в разделе «Ошибки HTTP-ответов».
errors[].code integer Код технической ошибки.
errors[].message string Описание технической ошибки.

Что делать дальше

  1. Безопасно повторите тот же GET-запрос с тем же X-Api-Key.
  2. При повторяющейся ошибке обратитесь в поддержку KVELL и передайте URL, время запроса, HTTP-код, code и message.

Как формируется криптограмма

Работа с карточными данными

Реквизиты карты шифруются на стороне мерчанта и в KVELL передаются только в виде криптограммы. Не сохраняйте и не логируйте номер карты и CVV, включая промежуточные значения до шифрования.

Криптограмма формируется в два шага.

  1. Соберите строку с реквизитами карты. Поля разделяются двумя двоеточиями, порядок фиксированный:

    pan::cvv::holder::exp_date
    
    Поле Обязательно Описание
    pan Да Номер карты.
    cvv Нет Проверочный код карты.
    holder Нет Имя держателя карты.
    exp_date Нет Срок действия карты, ровно 5 символов в формате ГГ/ММ, например 26/01.

    Необязательные поля можно опустить, обрезав строку справа: значения 4111111111111111::123 и 4111111111111111 также корректны. Пропустить поле в середине нельзя — пустое значение будет разобрано как пустая строка, а не как отсутствующее.

  2. Зашифруйте строку публичным ключом и закодируйте результат в Base64.

    Параметр Значение
    Алгоритм RSA-OAEP
    Хеш-функция SHA-256
    Функция формирования маски MGF1 с SHA-256
    Метка (label) Не используется
    Кодирование результата Base64 со стандартным алфавитом и выравниванием

При длине ключа 2048 бит и OAEP с SHA-256 максимальная длина исходной строки — 190 байт. Полные реквизиты карты в этот предел укладываются.

Примеры

В примерах используется тестовая карта 4111111111111111 со сроком действия 2034-12, который в криптограмме записывается как 34/12.

Требуется пакет: pip install cryptography.

import base64

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key

# значение поля public_key из ответа метода
public_key_pem = '-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n'

public_key = load_pem_public_key(public_key_pem.encode('utf-8'))

payload = '4111111111111111::123::TEST HOLDER::34/12'

cryptogram = base64.b64encode(
    public_key.encrypt(
        payload.encode('utf-8'),
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None,
        ),
    )
).decode('ascii')

print(cryptogram)

Используется встроенный модуль node:crypto.

const crypto = require('node:crypto');

// значение поля public_key из ответа метода
const publicKeyPem = '-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n';

const payload = '4111111111111111::123::TEST HOLDER::34/12';

const cryptogram = crypto.publicEncrypt(
  {
    key: publicKeyPem,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: 'sha256',
  },
  Buffer.from(payload, 'utf8'),
).toString('base64');

console.log(cryptogram);
# 1. Сохраните публичный ключ в файл
curl -s --request GET \
  --url 'https://api.pay.stage.kvell.group/v1/cryptogram/public-key' \
  --header 'X-Api-Key: 00000000-0000-4000-8000-000000000000' \
  | jq -r '.public_key' > public_key.pem

# 2. Зашифруйте реквизиты карты и закодируйте результат в Base64
printf '%s' '4111111111111111::123::TEST HOLDER::34/12' \
  | openssl pkeyutl -encrypt -pubin -inkey public_key.pem \
      -pkeyopt rsa_padding_mode:oaep \
      -pkeyopt rsa_oaep_md:sha256 \
      -pkeyopt rsa_mgf1_md:sha256 \
  | openssl base64 -A

Кэширование и ротация ключа

Ключ можно кэшировать. Поле refresh_after указывает момент, до которого возвращённое значение гарантированно актуально; после его наступления запросите ключ заново. Запрашивать ключ перед каждой операцией не требуется.

Ротация ключа не требует синхронного перехода: криптограммы, зашифрованные ключом, полученным до ротации, ещё некоторое время принимаются. Чтобы не зависеть от этого запаса, соблюдайте refresh_after.

Значение key_id меняется вместе с ключом, поэтому по нему удобно определить, что ключ обновился, и указать его при обращении в поддержку.