Работа с подписью к данным
Общая информация
Для обеспечения защищённого обмена данными при работе с платёжной платформой ecommpay используется криптографический протокол TLS (Transport Layer Security; протокол защиты транспортного уровня) версии не ниже 1.2, а для подтверждения авторства и целостности передаваемых данных дополнительно применяется цифровая подпись. Она формируется и проверяется по заданным алгоритмам с использованием одинакового секретного ключа, доступного на двух сторонах: мерчанта и ecommpay.
Независимо от интерфейса, используемого для работы с платформой, подпись обязательна к включению в состав всех программных запросов и оповещений (по запросам, выполняемым в рамках асинхронной схемы взаимодействия), а также выборочно применяется для ответов (по запросам, выполняемым в рамках синхронной схемы). Без подписи могут оставаться ответы с исключительно служебной информацией (например, об отклонении запроса из-за его некорректности) или с информацией общего характера, не раскрывающей специфику конкретного платежа или данные пользователя (например, в ответе со списком доступных банков). В остальных случаях подпись включается в состав ответов.
Для корректной работы перед отправкой в платформу любого запроса необходимо сформировать подпись и включить её в состав этого запроса, а при получении от платформы оповещений и ответов, содержащих подпись, следует проверять целостность данных путём сличения расчётных подписей с полученными. Для этого можно использовать как собственные программные решения, так и SDK от ecommpay (подробнее). Нарушения целостности данных могут быть вызваны разными причинами, но при обнаружении таких нарушений данные не могут и не должны считаться достоверными.
Далее описаны алгоритмы подписывания данных и проверки их целостности, а также представлены примеры выполнения этих алгоритмов и интерактивные формы для самостоятельной проверки корректной работы с подписью к разным данным.
Подписывание данных
Описание алгоритма
В качестве входных данных для подписывания выступают:
- Данные, которые требуется подписать.
Как правило, это заполненное тело запроса в формате JSON без включения в него параметра
signature
. - Ключ, используемый для подписывания.
Для отладки и тестирования алгоритма подписывания могут использоваться произвольные ключи. Для рабочих запросов в платформу следует применять только актуальный секретный ключ
В качестве выходных данных подписывания в зависимости от реализации алгоритма могут выступать либо подпись, либо подписанные данные — как правило, это итоговое тело запроса в формате JSON с параметром signature
, включённым в его состав.
Далее в описании, примере и форме для тестирования представлен часто используемый и наиболее показательный вариант реализации алгоритма: с телом запроса в формате JSON на входе (без параметра signature
) и на выходе (с параметром signature
).
В состав алгоритма в этом случае включаются следующие шаги:
-
Проверка входных данных на соблюдение заданных требований:
- Структура данных для подписывания должна соответствовать формату JSON.
- В составе данных для подписывания не должен присутствовать параметр
signature
(даже с пустым значением). - Должен быть задан ключ.
-
Преобразование данных в строку UTF-8 с сортировкой параметров в естественном порядке. В рамках этого шага выполняются следующие действия:
- Логические (булевы) значения кодируются следующим образом:
false
заменяется на0
, аtrue
— на1
. Но это относится только к булевым значениям — в строковых параметрах, даже если они содержат значенияfalse
илиtrue
, замены на0
или1
не используются (например, в параметреrecurring: "{type: \"U\",register: true}"
заменаtrue
на1
не требуется). - Каждый параметр преобразуется в строку, содержащую полный путь к параметру, название параметра и его значение:
<родительский_узел_1>:...:<родительский_узел_N>:<название_параметра>:<значение_параметра>
где родительские узлы — это названия объектов и (или) массивов, в состав которых включена пара «название параметра — значение параметра». Родительские узлы располагаются в порядке их вложения, начиная с самого верхнего уровня. В качестве разделителя при этом используется двоеточие (:), между парами «название параметра — значение параметра» удаляются запятые, а у строковых значений параметров удаляются обрамляющие кавычки.
- Параметры с нулевыми, а также пустыми значениями остаются в строке, например запись
"payment_description":""
представляется в видеpayment_description:
. Замены пустых значений на пробелы илиnull
не применяются. - Элементы массивов записываются отдельными строками, с указанием номера каждого элемента, начиная с нуля. Например, массив
["alpha", "beta", "gamma"]
представляется в виде трёх строк:0:alpha
,1:beta
и2:gamma
. - Пустые массивы полностью игнорируются и не включаются в набор строк для создания подписи.
- Кодировка всех строк приводится к формату UTF-8.
- Полученные строки упорядочиваются в естественном порядке и объединяются в одну строку с использованием в качестве разделителя точки с запятой (;).
- Логические (булевы) значения кодируются следующим образом:
- Получение двоичного кода HMAC с использованием ключа и функции SHA-512. На этом шаге для полученной строки с параметрами вычисляется HMAC (Hash-based Message Authentication Code; код аутентификации сообщений с использованием хеш-функции) с использованием функции хеширования SHA‑512 (Secure Hash Algorithm; безопасный алгоритм хеширования) и применяемого ключа. И этот HMAC представляется в виде необработанных двоичных данных.
- Кодирование двоичного кода HMAC с применением алгоритма Base64. На этом шаге полученный двоичный код HMAC кодируется с использованием алгоритма Base64. Получаемая при этом строка является подписью к исходным данным.
- Добавление подписи к данным. На этом шаге к исходным данным для подписывания добавляется параметр
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
и добавить его в запрос. Для этого необходимо:
- Убедиться, что в теле запроса нет параметра
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" } }
- Преобразовать оставшиеся параметры в строки 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
- Отсортировать полученные строки в естественном порядке:
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
- Объединить отсортированные строки в одну строку с использованием в качестве разделителя точки с запятой:
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
- Вычислить HMAC полученной строки с использованием функции хеширования SHA-512 и используемого ключа, после чего кодировать двоичный код HMAC с применением алгоритма Base64:
VLLZzVNGevQNhr1b4TEhbC4qqHD17Kyn/M6FPNN93ttyk/amJgD/R6dayTKVvW6/QCRdq4hOf8R2w/xbUa8f2w==
- Добавить полученную подпись в тело запроса:
{ "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" } }
Форма для тестирования
Проверка данных
Описание алгоритма
В качестве входных данных для проверки целостности выступают:
- Подписанные данные, которые требуется проверить. Как правило, это тело оповещения или ответа в формате JSON с параметром
signature
в его составе. - Ключ, используемый для проверки. Это должен быть ровно тот ключ, который был использован для подписывания проверяемых данных.
В качестве выходных данных при проверке целостности в зависимости от реализации алгоритма могут выступать расчётная подпись и информация о её совпадении с проверяемой подписью, то есть информация о целостности проверяемых данных.
Далее в описании, примере и форме для тестирования представлен часто используемый и наиболее показательный вариант реализации алгоритма: с телом проверяемого сообщения (оповещения или ответа) в формате JSON на входе и с заключением о целостности этого сообщения на выходе.
В состав алгоритма в этом случае включаются следующие шаги:
Рис.: Шаги алгоритма проверки данных
- Проверка входных данных на соблюдение заданных требований:
- Структура проверяемых данных должна соответствовать формату JSON.
- В составе проверяемых данных должен присутствовать параметр
signature
с подписью. - Должен быть задан проверочный ключ.
- Извлечение подписи из проверяемых данных. На этом шаге из проверяемых данных исключается параметр
signature
, а его значение фиксируется для последующего сличения с расчётной подписью. - Формирование расчётной подписи для проверяемых данных:
- Преобразование данных в строку UTF-8 с сортировкой параметров в естественном порядке. В рамках этого шага выполняются следующие действия:
- Логические (булевы) значения кодируются следующим образом:
false
заменяется на0
, аtrue
— на1
. Но это относится только к булевым значениям — в строковых параметрах, даже если они содержат значенияfalse
илиtrue
, замены на0
или1
не используются (например, в параметреrecurring: "{type: \"U\",register: true}"
заменаtrue
на1
не требуется). - Каждый параметр преобразуется в строку, содержащую полный путь к параметру, название параметра и его значение:
<родительский_узел_1>:...:<родительский_узел_N>:<название_параметра>:<значение_параметра>
, где родительские узлы — это названия объектов и (или) массивов, в состав которых включена пара «название параметра — значение параметра». Родительские узлы располагаются в порядке их вложения, начиная с самого верхнего уровня. В качестве разделителя при этом используется двоеточие (:), между парами «название параметра — значение параметра» удаляются запятые, а у строковых значений параметров удаляются обрамляющие кавычки. - Параметры с нулевыми, а также пустыми значениями остаются в строке, например запись
"payment_description":""
представляется в видеpayment_description:
. Замены пустых значений на пробелы илиnull
не применяются. - Элементы массивов записываются отдельными строками, с указанием номера каждого элемента, начиная с нуля. Например, массив
["alpha", "beta", "gamma"]
представляется в виде трёх строк:0:alpha
,1:beta
и2:gamma
. - Пустые массивы полностью игнорируются и не включаются в набор строк для создания подписи.
- Кодировка всех строк приводится к формату UTF-8.
- Полученные строки упорядочиваются в естественном порядке и объединяются в одну строку с использованием в качестве разделителя точки с запятой (;).
- Логические (булевы) значения кодируются следующим образом:
- Получение двоичного кода HMAC с использованием ключа и функции SHA‑512. На этом шаге для полученной строки с параметрами вычисляется HMAC (Hash-based Message Authentication Code; код аутентификации сообщений с использованием хеш-функции) с использованием функции хеширования SHA‑512 (Secure Hash Algorithm; безопасный алгоритм хеширования) и применяемого ключа. И этот HMAC представляется в виде необработанных двоичных данных.
- Кодирование двоичного кода HMAC с применением алгоритма Base64. На этом шаге полученный двоичный код HMAC кодируется с использованием алгоритма Base64. Получаемая при этом строка является подписью к исходным данным.
- Преобразование данных в строку UTF-8 с сортировкой параметров в естественном порядке. В рамках этого шага выполняются следующие действия:
- Сопоставление подписей. На этом шаге расчётная подпись сопоставляется с проверяемой. Если подписи совпадают, данные признаются целостными и достоверными. При несовпадении подписей данные не могут считаться достоверными и не должны использоваться в качестве рабочих.
Пример для оповещения
Допустим, что надо проверить подпись оповещения при следующих условиях:
- Используемый ключ —
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" } ] }
Для проверки подписи необходимо:
- Удалить из тела оповещения параметр
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" } ] }
- Преобразовать оставшиеся параметры в строки 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
- Отсортировать полученные строки в естественном порядке.
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
- Объединить отсортированные строки в одну строку с использованием в качестве разделителя точки с запятой.
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
- Вычислить HMAC полученной строки с использованием функции хеширования SHA-512 и используемого ключа, после чего кодировать двоичный код HMAC с применением алгоритма Base64.
rnv1OS3PJUKEJ5kw5wqoK0ftZGSd4Q6LX5A5NxK6d5alpND4sQTRFt7/9aFV+m3SRwNB8ba98GMsOY91yTVhEQ==
- Сравнить полученную подпись с проверяемой.
В данном случае подписи не совпадают, а это значит, что такое оповещение недостоверно или ошибочно и должно быть отброшено.
Форма для тестирования