Работа с подписью к данным

Общая информация

Для обеспечения защищённого обмена данными при работе с платёжной платформой ecommpay используется криптографический протокол TLS (Transport Layer Security; протокол защиты транспортного уровня) версии не ниже 1.2, а для подтверждения авторства и целостности передаваемых данных дополнительно применяется цифровая подпись. Она формируется и проверяется по заданным алгоритмам с использованием одинакового секретного ключа, доступного на двух сторонах: мерчанта и ecommpay.

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

Для корректной работы перед отправкой в платформу любого запроса необходимо сформировать подпись и включить её в состав этого запроса, а при получении от платформы оповещений и ответов, содержащих подпись, следует проверять целостность данных путём сличения расчётных подписей с полученными. Для этого можно использовать как собственные программные решения, так и SDK от ecommpay (подробнее). Нарушения целостности данных могут быть вызваны разными причинами, но при обнаружении таких нарушений данные не могут и не должны считаться достоверными.

Далее описаны алгоритмы подписывания данных и проверки их целостности, а также представлены примеры выполнения этих алгоритмов и интерактивные формы для самостоятельной проверки корректной работы с подписью к разным данным.

Подписывание данных

Описание алгоритма

В качестве входных данных для подписывания выступают:

  1. Данные, которые требуется подписать.

    Как правило, это заполненное тело запроса в формате JSON без включения в него параметра signature.

  2. Ключ, используемый для подписывания.

    Для отладки и тестирования алгоритма подписывания могут использоваться произвольные ключи. Для рабочих запросов в платформу следует применять только актуальный секретный ключ

В качестве выходных данных подписывания в зависимости от реализации алгоритма могут выступать либо подпись, либо подписанные данные — как правило, это итоговое тело запроса в формате JSON с параметром signature, включённым в его состав.

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

В состав алгоритма в этом случае включаются следующие шаги:

  1. Проверка входных данных на соблюдение заданных требований:

    1. Структура данных для подписывания должна соответствовать формату JSON.
    2. В составе данных для подписывания не должен присутствовать параметр signature (даже с пустым значением).
    3. Должен быть задан ключ.
  2. Преобразование данных в строку UTF-8 с сортировкой параметров в естественном порядке. В рамках этого шага выполняются следующие действия:

    1. Логические (булевы) значения кодируются следующим образом: false заменяется на 0, а true — на 1. Но это относится только к булевым значениям — в строковых параметрах, даже если они содержат значения false или true, замены на 0 или 1 не используются (например, в параметре recurring: "{type: \"U\",register: true}" замена true на 1 не требуется).
    2. Каждый параметр преобразуется в строку, содержащую полный путь к параметру, название параметра и его значение:
      <родительский_узел_1>:...:<родительский_узел_N>:<название_параметра>:<значение_параметра>

      где родительские узлы — это названия объектов и (или) массивов, в состав которых включена пара «название параметра — значение параметра». Родительские узлы располагаются в порядке их вложения, начиная с самого верхнего уровня. В качестве разделителя при этом используется двоеточие (:), между парами «название параметра — значение параметра» удаляются запятые, а у строковых значений параметров удаляются обрамляющие кавычки.

    3. Параметры с нулевыми, а также пустыми значениями остаются в строке, например запись "payment_description":"" представляется в виде payment_description:. Замены пустых значений на пробелы или null не применяются.
    4. Элементы массивов записываются отдельными строками, с указанием номера каждого элемента, начиная с нуля. Например, массив ["alpha", "beta", "gamma"] представляется в виде трёх строк: 0:alpha, 1:beta и 2:gamma.
    5. Пустые массивы полностью игнорируются и не включаются в набор строк для создания подписи.
    6. Кодировка всех строк приводится к формату UTF-8.
    7. Полученные строки упорядочиваются в естественном порядке и объединяются в одну строку с использованием в качестве разделителя точки с запятой (;).
  3. Получение двоичного кода HMAC с использованием ключа и функции SHA-512. На этом шаге для полученной строки с параметрами вычисляется HMAC (Hash-based Message Authentication Code; код аутентификации сообщений с использованием хеш-функции) с использованием функции хеширования SHA‑512 (Secure Hash Algorithm; безопасный алгоритм хеширования) и применяемого ключа. И этот HMAC представляется в виде необработанных двоичных данных.
  4. Кодирование двоичного кода HMAC с применением алгоритма Base64. На этом шаге полученный двоичный код HMAC кодируется с использованием алгоритма Base64. Получаемая при этом строка является подписью к исходным данным.
  5. Добавление подписи к данным. На этом шаге к исходным данным для подписывания добавляется параметр signature с полученной подписью в качестве его значения.

Пример для запроса на оплату через Gate

Допустим, что надо подписать запрос в Gate при следующих условиях:

  • Используемый ключ — secret
  • Предварительная версия тела запроса, в котором еще нет значения параметра с подписью, выглядит так:
    {
        "general": {
            "project_id": 3254,
            "payment_id": "id_38202316",
           "signature": "<подпись, которую нужно создать>"
        },
        "customer": {
            "id": "585741",
            "email": "johndoe@mycompany.com",
            "first_name": "John",
            "last_name": "Doe",
            "address": "Downing str., 23",
            "identify": {
                "doc_number": "54122312544"
            },
            "ip_address": "111.222.333.444"
        },
        "payment": {
            "amount": 10800,
            "currency": "USD",
            "description": "Computer keyboards"
        },
        "receipt_data": {
            "positions": [
                {
                    "quantity": "10",
                    "amount": "108",
                    "description": "Computer keyboard"
                }
            ]
        },
        "return_url": {
            "success": "https://paymentpage.mycompany.com/complete-redirect?id=success",
            "decline": "https://paymentpage.mycompany.com/complete-redirect?id=decline"
        }
    }

Задача заключается в том, чтобы вычислить подпись, то есть рассчитать значение параметра signature и добавить его в запрос. Для этого необходимо:

  1. Убедиться, что в теле запроса нет параметра signature, даже с пустым значением. Если такой параметр есть, его нужно удалить.
    {
        "general": {
            "project_id": 3254,
            "payment_id": "id_38202316",
           "signature": "<подпись, которую нужно создать>"
        },
        "customer": {
            "id": "585741",
            "email": "johndoe@mycompany.com",
            "first_name": "John",
            "last_name": "Doe",
            "address": "Downing str., 23",
            "identify": {
                "doc_number": "54122312544"
            },
            "ip_address": "111.222.333.444"
        },
        "payment": {
            "amount": 10800,
            "currency": "USD",
            "description": "Computer keyboards"
        },
        "receipt_data": {
            "positions": [
                {
                    "quantity": "10",
                    "amount": "108",
                    "description": "Computer keyboard"
                }
            ]
        },
        "return_url": {
            "success": "https://paymentpage.mycompany.com/complete-redirect?id=success",
            "decline": "https://paymentpage.mycompany.com/complete-redirect?id=decline"
        }
    }
  2. Преобразовать оставшиеся параметры в строки UTF-8 согласно правилам алгоритма:
    general:project_id:3254
    general:payment_id:id_38202316
    customer:id:585741
    customer:email:johndoe@mycompany.com
    customer:first_name:John
    customer:last_name:Doe
    customer:address:Downing str., 23
    customer:identify:doc_number:54122312544
    customer:ip_address:111.222.333.444
    payment:amount:10800
    payment:currency:USD
    payment:description:Computer keyboards
    receipt_data:positions:0:quantity:10
    receipt_data:positions:0:amount:108
    receipt_data:positions:0:description:Computer keyboard
    return_url:success:https://paymentpage.mycompany.com/complete-redirect?id=success
    return_url:decline:https://paymentpage.mycompany.com/complete-redirect?id=decline
  3. Отсортировать полученные строки в естественном порядке:
    customer:address:Downing str., 23
    customer:email:johndoe@mycompany.com
    customer:first_name:John
    customer:id:585741
    customer:identify:doc_number:54122312544
    customer:ip_address:111.222.333.444
    customer:last_name:Doe
    general:payment_id:id_38202316
    general:project_id:3254
    payment:amount:10800
    payment:currency:USD
    payment:description:Computer keyboards
    receipt_data:positions:0:amount:108
    receipt_data:positions:0:description:Computer keyboard
    receipt_data:positions:0:quantity:10
    return_url:decline:https://paymentpage.mycompany.com/complete-redirect?id=decline
    return_url:success:https://paymentpage.mycompany.com/complete-redirect?id=success
    
  4. Объединить отсортированные строки в одну строку с использованием в качестве разделителя точки с запятой:
    customer:address:Downing str., 23;customer:email:johndoe@mycompany.com;customer:first_name:John;customer:id:585741;customer:identify:doc_number:54122312544;customer:ip_address:111.222.333.444;customer:last_name:Doe;general:payment_id:id_38202316;general:project_id:3254;payment:amount:10800;payment:currency:USD;payment:description:Computer keyboards;receipt_data:positions:0:amount:108;receipt_data:positions:0:description:Computer keyboard;receipt_data:positions:0:quantity:10;return_url:decline:https://paymentpage.mycompany.com/complete-redirect?id=decline;return_url:success:https://paymentpage.mycompany.com/complete-redirect?id=success
  5. Вычислить HMAC полученной строки с использованием функции хеширования SHA-512 и используемого ключа, после чего кодировать двоичный код HMAC с применением алгоритма Base64:
    VLLZzVNGevQNhr1b4TEhbC4qqHD17Kyn/M6FPNN93ttyk/amJgD/R6dayTKVvW6/QCRdq4hOf8R2w/xbUa8f2w==
  6. Добавить полученную подпись в тело запроса:
    {
        "general": {
            "project_id": 3254,
            "payment_id": "id_38202316",
           "signature": "VLLZzVNGevQNhr1b4TEhbC4qqHD17Kyn/M6FPNN93ttyk/amJgD/R6dayTKVvW6/QCRdq4hOf8R2w/xbUa8f2w=="
        },
        "customer": {
            "id": "585741",
            "email": "johndoe@mycompany.com",
            "first_name": "John",
            "last_name": "Doe",
            "address": "Downing str., 23",
            "identify": {
                "doc_number": "54122312544"
            },
            "ip_address": "111.222.333.444"
        },
        "payment": {
            "amount": 10800,
            "currency": "USD",
            "description": "Computer keyboards"
        },
        "receipt_data": {
            "positions": [
                {
                    "quantity": "10",
                    "amount": "108",
                    "description": "Computer keyboard"
                }
            ]
        },
        "return_url": {
            "success": "https://paymentpage.mycompany.com/complete-redirect?id=success",
            "decline": "https://paymentpage.mycompany.com/complete-redirect?id=decline"
        }
    }

Форма для тестирования

Проверка данных

Описание алгоритма

В качестве входных данных для проверки целостности выступают:

  1. Подписанные данные, которые требуется проверить. Как правило, это тело оповещения или ответа в формате JSON с параметром signature в его составе.
  2. Ключ, используемый для проверки. Это должен быть ровно тот ключ, который был использован для подписывания проверяемых данных.

В качестве выходных данных при проверке целостности в зависимости от реализации алгоритма могут выступать расчётная подпись и информация о её совпадении с проверяемой подписью, то есть информация о целостности проверяемых данных.

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

В состав алгоритма в этом случае включаются следующие шаги:

Рис.: Шаги алгоритма проверки данных

  1. Проверка входных данных на соблюдение заданных требований:
    1. Структура проверяемых данных должна соответствовать формату JSON.
    2. В составе проверяемых данных должен присутствовать параметр signature с подписью.
    3. Должен быть задан проверочный ключ.
  2. Извлечение подписи из проверяемых данных. На этом шаге из проверяемых данных исключается параметр signature, а его значение фиксируется для последующего сличения с расчётной подписью.
  3. Формирование расчётной подписи для проверяемых данных:
    1. Преобразование данных в строку UTF-8 с сортировкой параметров в естественном порядке. В рамках этого шага выполняются следующие действия:
      1. Логические (булевы) значения кодируются следующим образом: false заменяется на 0, а true — на 1. Но это относится только к булевым значениям — в строковых параметрах, даже если они содержат значения false или true, замены на 0 или 1 не используются (например, в параметре recurring: "{type: \"U\",register: true}" замена true на 1 не требуется).
      2. Каждый параметр преобразуется в строку, содержащую полный путь к параметру, название параметра и его значение: <родительский_узел_1>:...:<родительский_узел_N>:<название_параметра>:<значение_параметра>, где родительские узлы — это названия объектов и (или) массивов, в состав которых включена пара «название параметра — значение параметра». Родительские узлы располагаются в порядке их вложения, начиная с самого верхнего уровня. В качестве разделителя при этом используется двоеточие (:), между парами «название параметра — значение параметра» удаляются запятые, а у строковых значений параметров удаляются обрамляющие кавычки.
      3. Параметры с нулевыми, а также пустыми значениями остаются в строке, например запись "payment_description":"" представляется в виде payment_description:. Замены пустых значений на пробелы или null не применяются.
      4. Элементы массивов записываются отдельными строками, с указанием номера каждого элемента, начиная с нуля. Например, массив ["alpha", "beta", "gamma"] представляется в виде трёх строк: 0:alpha, 1:beta и 2:gamma.
      5. Пустые массивы полностью игнорируются и не включаются в набор строк для создания подписи.
      6. Кодировка всех строк приводится к формату UTF-8.
      7. Полученные строки упорядочиваются в естественном порядке и объединяются в одну строку с использованием в качестве разделителя точки с запятой (;).
    2. Получение двоичного кода HMAC с использованием ключа и функции SHA‑512. На этом шаге для полученной строки с параметрами вычисляется HMAC (Hash-based Message Authentication Code; код аутентификации сообщений с использованием хеш-функции) с использованием функции хеширования SHA‑512 (Secure Hash Algorithm; безопасный алгоритм хеширования) и применяемого ключа. И этот HMAC представляется в виде необработанных двоичных данных.
    3. Кодирование двоичного кода HMAC с применением алгоритма Base64. На этом шаге полученный двоичный код HMAC кодируется с использованием алгоритма Base64. Получаемая при этом строка является подписью к исходным данным.
  4. Сопоставление подписей. На этом шаге расчётная подпись сопоставляется с проверяемой. Если подписи совпадают, данные признаются целостными и достоверными. При несовпадении подписей данные не могут считаться достоверными и не должны использоваться в качестве рабочих.

Пример для оповещения

Допустим, что надо проверить подпись оповещения при следующих условиях:

  • Используемый ключ — secret
  • Тело полученного оповещения выглядит так:

    Рис.: Тело оповещения

    {
      "request_id": "17cd0535c5ffecf5-335e5b5e",
      "transaction": {
        "id": 82452138542211,
        "date": "2019-10-09T07:13:36+0000",
        "type": "purchase"
      },
      "payment": {
        "id": "123456789",
        "method": "webmoney",
        "date": "2019-10-09T07:13:36+0000",
        "result_code": "9999",
        "result_message": "Awaiting processing",
        "status": "awaiting redirect result",
        "is_new_attempts_available": false,
        "attempts_timeout": 0,
        "cascading_with_redirect": false,
        "provider_id": 1234
      },
      "sum_real": {
        "amount": 29100,
        "currency": "USD"
      },
      "account": {
        "number": "79879496816"
      },
      "return_url": {
        "method": "GET",
        "body": {
          "shop": "1234567",
          "transaction": 45712154,
          "iframe": "true",
          "successUrl": "https://mysite.com/process/success",
          "failUrl": "https://mysite.com/process/failure",
          "target": "iframe"
        },
        "encrypted": []
      },
      "rrn": "",
      "general": {
        "project_id": 123,
        "payment_id": "457822332658",
        "signature": "NtDutuRiksyHeBhhUs+nQxQ1FcMSueoACb4vENju0APgHgeZfRfMj46289v1vD4hJ1a8Yhg=="
      },
      "description": "TEST_1543831735980",
      "sum_request": {
        "amount": 29100,
        "currency": "USD"
      },
      "operations": [
        {
          "id": 45712154,
          "type": "sale",
          "status": "awaiting redirect result",
          "date": "2019-10-09T07:13:36+0000",
          "processing_time": null,
          "request_id": "3bd75dc1977cc8c05b50855b-544f7f6af3d989dd42ef8e6ff02df56eef7c5f4e-521457",
          "sum": {
            "amount": 29100,
            "currency": "USD"
          },
          "code": "9999",
          "message": "Awaiting processing"
        }
      ]
    }

Для проверки подписи необходимо:

  1. Удалить из тела оповещения параметр signature вместе с его значением.
    {
      "request_id": "17cd0535c5ffecf5-335e5b5e",
      "transaction": {
        "id": 82452138542211,
        "date": "2019-10-09T07:13:36+0000",
        "type": "purchase"
      },
      "payment": {
        "id": "123456789",
        "method": "webmoney",
        "date": "2019-10-09T07:13:36+0000",
        "result_code": "9999",
        "result_message": "Awaiting processing",
        "status": "awaiting redirect result",
        "is_new_attempts_available": false,
        "attempts_timeout": 0,
        "cascading_with_redirect": false,
        "provider_id": 1234
      },
      "sum_real": {
        "amount": 29100,
        "currency": "USD"
      },
      "account": {
        "number": "79879496816"
      },
      "return_url": {
        "method": "GET",
        "body": {
          "shop": "1234567",
          "transaction": 45712154,
          "iframe": "true",
          "successUrl": "https://mysite.com/process/success",
          "failUrl": "https://mysite.com/process/failure",
          "target": "iframe"
        },
        "encrypted": []
      },
      "rrn": "",
      "general": {
        "project_id": 123,
        "payment_id": "457822332658",
        "signature": "NtDutuRiksyHeBhhUs+nQxQ1FcMSueoACb4vENju0APgHgeZfRfMj46289v1vD4hJ1a8Yhg=="
      },
      "description": "TEST_1543831735980",
      "sum_request": {
        "amount": 29100,
        "currency": "USD"
      },
      "operations": [
        {
          "id": 45712154,
          "type": "sale",
          "status": "awaiting redirect result",
          "date": "2019-10-09T07:13:36+0000",
          "processing_time": null,
          "request_id": "3bd75dc1977cc8c05b50855b-544f7f6af3d989dd42ef8e6ff02df56eef7c5f4e-521457",
          "sum": {
            "amount": 29100,
            "currency": "USD"
          },
          "code": "9999",
          "message": "Awaiting processing"
        }
      ]
    }
  2. Преобразовать оставшиеся параметры в строки UTF-8 согласно правилам алгоритма.
    request_id:17cd0535c5ffecf5-335e5b5e
    
    transaction:id:82452138542211
    transaction:date:2019-10-09T07:13:36+0000
    transaction:type:purchase
    payment:id:123456789
    payment:method:webmoney
    payment:date:2019-10-09T07:13:36+0000
    payment:result_code:9999
    payment:result_message:Awaiting processing
    payment:status:awaiting redirect result
    payment:is_new_attempts_available:0
    payment:attempts_timeout:0
    payment:cascading_with_redirect:0
    payment:provider_id:1234
    sum_real:amount:29100
    sum_real:currency:USD
    account:number:79879496816
    return_url:method:GET
    return_url:body:shop:1234567
    return_url:body:transaction:45712154
    return_url:body:iframe:true
    return_url:body:successUrl:https://mysite.com/process/success
    return_url:body:failUrl:https://mysite.com/process/failure
    return_url:body:target:iframe
    rrn:
    general:project_id:123
    general:payment_id:457822332658
    description:TEST_1543831735980
    sum_request:amount:29100
    sum_request:currency:USD
    operations:0:id:45712154
    operations:0:type:sale
    operations:0:status:awaiting redirect result
    operations:0:date:2019-10-09T07:13:36+0000
    operations:0:processing_time:
    operations:0:request_id:3bd75dc1977cc8c05b50855b
    operations:0:sum:amount:29100
    operations:0:sum:currency:USD
    operations:0:code:9999
    operations:0:message:Awaiting processing
    
  3. Отсортировать полученные строки в естественном порядке.
    account:number:79879496816
    description:TEST_1543831735980
    general:payment_id:457822332658
    general:project_id:123
    operations:0:code:9999
    operations:0:date:2019-10-09T07:13:36+0000
    operations:0:id:45712154
    operations:0:message:Awaiting processing
    operations:0:processing_time:
    operations:0:request_id:3bd75dc1977cc8c05b50855b
    operations:0:status:awaiting redirect result
    operations:0:sum:amount:29100
    operations:0:sum:currency:USD
    operations:0:type:sale
    payment:attempts_timeout:0
    payment:cascading_with_redirect:0
    payment:date:2019-10-09T07:13:36+0000
    payment:id:123456789
    payment:is_new_attempts_available:0
    payment:method:webmoney
    payment:provider_id:1234
    payment:result_code:9999
    payment:result_message:Awaiting processing
    payment:status:awaiting redirect result
    request_id:17cd0535c5ffecf5-335e5b5e
    return_url:body:failUrl:https://mysite.com/process/failure
    return_url:body:iframe:true
    return_url:body:shop:1234567
    return_url:body:successUrl:https://mysite.com/process/success
    return_url:body:target:iframe
    return_url:body:transaction:45712154
    return_url:method:GET
    rrn:
    sum_real:amount:29100
    sum_real:currency:USD
    sum_request:amount:29100
    sum_request:currency:USD
    transaction:date:2019-10-09T07:13:36+0000
    transaction:id:82452138542211
    transaction:type:purchase
    
  4. Объединить отсортированные строки в одну строку с использованием в качестве разделителя точки с запятой.
    account:number:79879496816;description:TEST_1543831735980;general:payment_id:457822332658;general:project_id:123;operations:0:code:9999;operations:0:date:2019-10-09T07:13:36+0000;operations:0:id:45712154;operations:0:message:Awaiting processing;operations:0:processing_time:;operations:0:request_id:3bd75dc1977cc8c05b50855b-544f7f6af3d989dd42ef8e6ff02df56eef7c5f4e-521457;operations:0:status:awaiting redirect result;operations:0:sum:amount:29100;operations:0:sum:currency:USD;operations:0:type:sale;payment:attempts_timeout:0;payment:cascading_with_redirect:0;payment:date:2019-10-09T07:13:36+0000;payment:id:123456789;payment:is_new_attempts_available:0;payment:method:webmoney;payment:provider_id:1234;payment:result_code:9999;payment:result_message:Awaiting processing;payment:status:awaiting redirect result;request_id:17cd0535c5ffecf5-335e5b5e;return_url:body:failUrl:https://mysite.com/process/failure;return_url:body:iframe:true;return_url:body:shop:1234567;return_url:body:successUrl:https://mysite.com/process/success;return_url:body:target:iframe;return_url:body:transaction:45712154;return_url:method:GET;rrn:;sum_real:amount:29100;sum_real:currency:USD;sum_request:amount:29100;sum_request:currency:USD;transaction:date:2019-10-09T07:13:36+0000;transaction:id:82452138542211;transaction:type:purchase
  5. Вычислить HMAC полученной строки с использованием функции хеширования SHA-512 и используемого ключа, после чего кодировать двоичный код HMAC с применением алгоритма Base64.
    rnv1OS3PJUKEJ5kw5wqoK0ftZGSd4Q6LX5A5NxK6d5alpND4sQTRFt7/9aFV+m3SRwNB8ba98GMsOY91yTVhEQ==
  6. Сравнить полученную подпись с проверяемой.

    В данном случае подписи не совпадают, а это значит, что такое оповещение недостоверно или ошибочно и должно быть отброшено.

Форма для тестирования