Перейти до основного вмісту

Підпис HTML-форми

Будь-яке поле форми покупець може змінити прямо в браузері — наприклад, поставити меншу суму. Тому поля рахунку передаються в підписаному контейнері: сервіс обчислює підпис заново й, якщо він не збігся, рахунок не створює.

Підпис обчислює сервер магазину. У браузер іде лише готова пара data і signature.

Що підписується

data = base64url(json)
signature = HMAC-SHA256(data, formSecret)

HMAC обчислюється від рядка data, а не від байтів JSON. Приймач звіряє підпис із надісланим рядком як він є й лише потім розкодовує його.

Звідси випливає, що канонізації немає: порядок ключів, відступи в JSON і спосіб екранування юнікоду на підпис не впливають. Підходить будь-який штатний JSON-кодувальник.

Еталонний набір

Підставте ці значення у свій код і звірте результат.

Секрет для прикладу:

dvc3khto5WpjEPojZfYJsbJzFEPPVnRa

Сума та строк оплати числами, рядком вони теж приймаються:

{"projectId":"a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d","amountFiat":10.5,"currencyFiat":"USD","timeToPay":1,"description":"Order #7, delivery","serviceData":"order-7"}

Рядок data — base64url цих байтів. Падинг тут відкинуто, приймач приймає й із ним:

eyJwcm9qZWN0SWQiOiJhMWIyYzNkNC01ZTZmLTRhN2ItOGM5ZC0wZTFmMmEzYjRjNWQiLCJhbW91bnRGaWF0IjoxMC41LCJjdXJyZW5jeUZpYXQiOiJVU0QiLCJ0aW1lVG9QYXkiOjEsImRlc2NyaXB0aW9uIjoiT3JkZXIgIzcsIGRlbGl2ZXJ5Iiwic2VydmljZURhdGEiOiJvcmRlci03In0

Підпис цього рядка на секреті вище:

09628020c47400c9231420450af642adfdb3c246f33e3e12cb8e28f96c2a785e

Приклади нижче з цими значеннями друкують саме такі data і підпис. Той самий JSON із ключем "output":"errors" наприкінці дає інший рядок data та інший підпис.

Якщо data не збіглася, а JSON виглядає так само — річ у кодувальнику JSON: інший порядок ключів або пробіли після розділювачів. Приймач прийме й такий контейнер, але з еталоном він дослівно не зійдеться.

Приклади

<?php

function signedContainer(array $fields, string $formSecret): array {
$data = rtrim(strtr(base64_encode(json_encode($fields)), '+/', '-_'), '=');

return [
'data' => $data,
'signature' => hash_hmac('sha256', $data, $formSecret),
];
}

// Test values from the reference set. Before going live,
// replace the secret with the form secret from your project settings
$container = signedContainer([
'projectId' => 'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
'amountFiat' => 10.5,
'currencyFiat' => 'USD',
'timeToPay' => 1,
'description' => 'Order #7, delivery',
'serviceData' => 'order-7',
], 'dvc3khto5WpjEPojZfYJsbJzFEPPVnRa');

echo $container['data'], "\n";
echo $container['signature'], "\n";

Значення в прикладах латиницею, але код працює з будь-яким юнікодом без змін. PHP і Python за замовчуванням екранують не-ASCII в \uXXXX, JavaScript надсилає сирий UTF-8 — приймач приймає обидва види.

Дві часті помилки

Підписано JSON замість контейнера. HMAC обчислюється від рядка data, тобто від base64url. Помилка виглядає як «усе збігається, а підпис ні».

Контейнер перезібрано після підпису. Змінився хоч байт у data — підпис недійсний. Збирайте пару в одному місці коду.

Якщо підпис не сходиться

Ідіть по ланцюжку JSON → data → підпис: де перша ланка розійшлася з еталоном — там і причина. Спершу звірте data: він не залежить від секрету.