Работа с подписью к данным
Общая информация
Для обеспечения защищённого обмена данными при работе с платёжной платформой ecommpay используется криптографический протокол TLS (Transport Layer Security; протокол защиты транспортного уровня) версии не ниже 1.2, а для подтверждения авторства и целостности передаваемых данных дополнительно применяется цифровая подпись. Она формируется и проверяется по заданным алгоритмам с использованием одинакового секретного ключа, доступного на двух сторонах: мерчанта и ecommpay.
Независимо от интерфейса, используемого для работы с платформой, подпись обязательна к включению в состав всех программных запросов и оповещений (по запросам, выполняемым в рамках асинхронной схемы взаимодействия), а также выборочно применяется для ответов (по запросам, выполняемым в рамках синхронной схемы). Без подписи могут оставаться ответы с исключительно служебной информацией (например, об отклонении запроса из-за его некорректности) или с информацией общего характера, не раскрывающей специфику конкретного платежа или данные пользователя (например, в ответе со списком доступных банков). В остальных случаях подпись включается в состав ответов.
Для корректной работы перед отправкой в платформу любого запроса необходимо сформировать подпись и включить её в состав этого запроса, а при получении от платформы оповещений и ответов, содержащих подпись, следует проверять целостность данных путём сличения расчётных подписей с полученными. Для этого можно использовать как собственные программные решения, так и SDK от ecommpay (подробнее). Нарушения целостности данных могут быть вызваны разными причинами, но при обнаружении таких нарушений данные не могут и не должны считаться достоверными.
Далее описаны алгоритмы подписывания данных и проверки их целостности, а также представлены примеры выполнения этих алгоритмов и интерактивные формы для самостоятельной проверки корректной работы с подписью к разным данным.
Подписывание данных
Описание алгоритма
В качестве входных данных для подписывания выступают:
- Данные, которые требуется подписать.
Как правило, это все параметры запроса за исключением подписи или JavaScript-объект configObj с параметрами без включения параметра
signature
. - Ключ, используемый для подписывания.
Для отладки и тестирования алгоритма подписывания могут использоваться произвольные ключи. Для рабочих запросов в платформу следует применять только актуальный секретный ключ
В качестве выходных данных подписывания в зависимости от реализации алгоритма могут выступать либо подпись, либо подписанный JavaScript-объект configObj — как правило, это итоговый объект с параметром signature
, включённым в его состав.
Далее в описании, примере и форме для тестирования представлен часто используемый и наиболее показательный вариант реализации алгоритма: с JavaScript-объектом configObj на входе (без параметра signature
) и на выходе (с параметром signature
).
Рис.: Шаги алгоритма подписывания данных
- Проверка входных данных на соблюдение заданных требований:
- Структура данных для подписывания должна соответствовать формату JavaScript-объектов.
- В составе данных для подписывания не должен присутствовать параметр
signature
(даже с пустым значением). - Должен быть задан ключ.
- Преобразование данных в строку UTF-8 с сортировкой параметров по алфавиту. В рамках этого шага выполняются следующие действия:
- Логические (булевы) значения кодируются следующим образом:
false
заменяется на0
, аtrue
— на1
. Но это относится только к булевым значениям — в строковых параметрах, даже если они содержат значенияfalse
илиtrue
, замены на0
или1
не используются (например, в параметреrecurring: "{type: \"U\",register: true}"
заменаtrue
на1
не требуется). - Параметры с нулевыми, а также пустыми значениями остаются в строке, например запись
"payment_description":""
представляется в видеpayment_description:
. Замены пустых значений на пробелы илиnull
не применяются. - Кодировка всех строк приводится к формату UTF-8.
- Полученные строки упорядочиваются в алфавитном порядке и объединяются в одну строку с использованием в качестве разделителя точки с запятой (;).
- Логические (булевы) значения кодируются следующим образом:
- Получение двоичного кода HMAC с использованием ключа и функции SHA-512. На этом шаге для полученной строки с параметрами вычисляется HMAC (Hash-based Message Authentication Code; код аутентификации сообщений с использованием хеш-функции) с использованием функции хеширования SHA‑512 (Secure Hash Algorithm; безопасный алгоритм хеширования) и применяемого ключа. И этот HMAC представляется в виде необработанных двоичных данных.
- Кодирование двоичного кода HMAC с применением алгоритма Base64. На этом шаге полученный двоичный код HMAC кодируется с использованием алгоритма Base64. Получаемая при этом строка является подписью к исходным данным.
- Добавление подписи к данным. На этом шаге к исходным данным для подписывания добавляется параметр
signature
с полученной подписью в качестве его значения.
Пример для запроса на оплату через Payment Page
Допустим, что надо подписать запрос на открытие Payment Page при следующих условиях:
- Используемый ключ —
secret
- Предварительная версия объекта configObj, в котором еще нет значения параметра с подписью, выглядит так:
{ "close_on_missclick": true, "project_id": 12345, "payment_id": "X03936", "payment_amount": 2035, "payment_currency": "USD", "payment_description": "Покупка наручных часов", "customer_first_name": "Иван", "customer_id": "user007", "customer_last_name": "Иванов", "customer_phone": "74991234567" "signature": "<подпись, которую нужно создать>" }
Задача заключается в том, чтобы вычислить подпись, то есть определить значение параметра signature
. Для этого необходимо:
- Убедиться, что в теле запроса нет параметра
signature
, даже с пустым значением. Если такой параметр есть, его нужно удалить:{ "close_on_missclick": true, "project_id": 12345, "payment_id": "X03936", "payment_amount": 2035, "payment_currency": "USD", "payment_description": "Покупка наручных часов", "customer_first_name": "Иван", "customer_id": "user007", "customer_last_name": "Иванов", "customer_phone": "74991234567" "signature": "<подпись, которую нужно создать>" }
- Преобразовать оставшиеся параметры в строки UTF-8 согласно правилам алгоритма:
close_on_missclick:1 project_id:12345 payment_id:X03936 payment_amount:2035 payment_currency:USD payment_description:Покупка наручных часов customer_first_name:Иван customer_id:user007 customer_last_name:Иванов customer_phone:74991234567
- Отсортировать полученные строки по алфавиту:
close_on_missclick:1 customer_first_name:Иван customer_id:user007 customer_last_name:Иванов customer_phone:74991234567 payment_amount:2035 payment_currency:USD payment_description:Покупка наручных часов payment_id:X03936 project_id:12345
- Объединить отсортированные строки в одну строку с использованием в качестве разделителя точки с запятой:
close_on_missclick:1;customer_first_name:Иван;customer_id:user007;customer_last_name:Иванов;customer_phone:74991234567;payment_amount:2035;payment_currency:USD;payment_description:Покупка наручных часов;payment_id:X03936;project_id:12345
- Вычислить HMAC полученной строки с использованием функции хеширования SHA-512 и используемого ключа, после чего кодировать двоичный код HMAC с применением алгоритма Base64:
qEdtMxgqjwDWJypjDZyQbmJShhOb+5tnqP6mGuURzF/u3LlUv0tOdEcPZ+ULYFOaRSLTkWvGcG4cZfKVLdVFXQ==
- Добавить полученную подпись в объект configObj:
{ "close_on_missclick": true, "project_id": 12345, "payment_id": "X03936", "payment_amount": 2035, "payment_currency": "USD", "payment_description": "Покупка наручных часов", "customer_first_name": "Иван", "customer_id": "user007", "customer_last_name": "Иванов", "customer_phone": "74991234567", "signature": "RX1bAYQxhZSsAWOi7aMSpKQ/sEST7nl/MOq9/NTFF57ePhJIFC65cVjTaa24SX44eycToTWBOr3YRftU6h9IFA==" }
Форма для тестирования
Проверка данных
Описание алгоритма
В качестве входных данных для проверки целостности выступают:
- Подписанные данные, которые требуется проверить. Как правило, это тело оповещения или ответа в формате 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==
- Сравнить полученную подпись с проверяемой.
В данном случае подписи не совпадают, а это значит, что такое оповещение недостоверно или ошибочно и должно быть отброшено.
Форма для тестирования