Tanda tangan formulir HTML
Pembeli dapat mengubah field formulir apa pun langsung di browser—misalnya, memasang jumlah yang lebih kecil. Karena itu field tagihan berjalan dalam kontainer yang ditandatangani: layanan menghitung ulang tanda tangan dan, jika tidak cocok, tidak membuat tagihan.
Server toko yang menghitung tanda tangan. Hanya pasangan data dan signature yang sudah jadi yang dikirim ke browser.
Apa yang ditandatangani
data = base64url(json)
signature = HMAC-SHA256(data, formSecret)
HMAC dihitung atas string data, bukan atas byte JSON. Penerima memverifikasi tanda tangan terhadap string yang dikirim apa adanya, baru kemudian mendekodenya.
Artinya tidak ada kanonikalisasi: urutan kunci, indentasi JSON, dan gaya escaping Unicode tidak memengaruhi tanda tangan. Encoder JSON standar apa pun bisa dipakai.
Set acuan
Masukkan nilai-nilai ini ke kode Anda dan bandingkan keluarannya.
Kunci rahasia contoh:
dvc3khto5WpjEPojZfYJsbJzFEPPVnRa
Jumlah dan batas waktu pembayaran sebagai angka; string juga diterima:
{"projectId":"a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d","amountFiat":10.5,"currencyFiat":"USD","timeToPay":1,"description":"Order #7, delivery","serviceData":"order-7"}
String data adalah base64url dari byte tersebut. Padding di sini dibuang; penerima juga menerimanya dengan padding:
eyJwcm9qZWN0SWQiOiJhMWIyYzNkNC01ZTZmLTRhN2ItOGM5ZC0wZTFmMmEzYjRjNWQiLCJhbW91bnRGaWF0IjoxMC41LCJjdXJyZW5jeUZpYXQiOiJVU0QiLCJ0aW1lVG9QYXkiOjEsImRlc2NyaXB0aW9uIjoiT3JkZXIgIzcsIGRlbGl2ZXJ5Iiwic2VydmljZURhdGEiOiJvcmRlci03In0
Tanda tangan string ini dengan kunci rahasia di atas:
09628020c47400c9231420450af642adfdb3c246f33e3e12cb8e28f96c2a785e
Dengan nilai-nilai ini, contoh di bawah mencetak data dan tanda tangan persis seperti ini. JSON yang sama dengan kunci "output":"errors" di akhirnya menghasilkan string data yang berbeda dan tanda tangan yang berbeda.
Jika data tidak cocok padahal JSON-nya terlihat sama, penyebabnya adalah encoder JSON: urutan kunci berbeda atau ada spasi setelah pemisah. Penerima tetap menerima kontainer semacam itu, tetapi tidak akan sama persis dengan acuan.
Contoh
- PHP
- TypeScript
- JavaScript
- Python
<?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";
import { createHmac } from 'node:crypto';
// Values are JSON strings or numbers, optional keys may be omitted
export interface FormFields {
projectId: string;
amountFiat: string | number;
currencyFiat: string;
timeToPay: string | number;
description?: string;
serviceData?: string;
output?: string;
}
export interface FormContainer {
data: string;
signature: string;
}
export const signedContainer = (fields: FormFields, formSecret: string): FormContainer => {
const data = Buffer.from(JSON.stringify(fields), 'utf8').toString('base64url');
return {
data,
signature: createHmac('sha256', formSecret).update(data, 'utf8').digest('hex'),
};
};
// Test values from the reference set. Before going live,
// replace the secret with the form secret from your project settings
const container = signedContainer({
projectId: 'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
amountFiat: 10.5,
currencyFiat: 'USD',
timeToPay: 1,
description: 'Order #7, delivery',
serviceData: 'order-7',
}, 'dvc3khto5WpjEPojZfYJsbJzFEPPVnRa');
console.log(container.data);
console.log(container.signature);
Ini kode untuk server toko, bukan browser: tanda tangan dihitung di tempat kunci rahasia disimpan.
import { createHmac } from 'node:crypto';
const signedContainer = (fields, formSecret) => {
const data = Buffer.from(JSON.stringify(fields), 'utf8').toString('base64url');
return {
data,
signature: createHmac('sha256', formSecret).update(data, 'utf8').digest('hex'),
};
};
// Test values from the reference set. Before going live,
// replace the secret with the form secret from your project settings
const container = signedContainer({
projectId: 'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
amountFiat: 10.5,
currencyFiat: 'USD',
timeToPay: 1,
description: 'Order #7, delivery',
serviceData: 'order-7',
}, 'dvc3khto5WpjEPojZfYJsbJzFEPPVnRa');
console.log(container.data);
console.log(container.signature);
import base64
import hashlib
import hmac
import json
def signed_container(fields: dict, form_secret: str) -> dict:
# compact separators: the default ones add spaces after ',' and ':'
body = json.dumps(fields, separators=(',', ':'))
data = base64.urlsafe_b64encode(body.encode('utf-8')).decode().rstrip('=')
return {
'data': data,
'signature': hmac.new(
form_secret.encode('utf-8'), data.encode('utf-8'), hashlib.sha256
).hexdigest(),
}
# Test values from the reference set. Before going live,
# replace the secret with the form secret from your project settings
container = signed_container({
'projectId': 'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
'amountFiat': 10.5,
'currencyFiat': 'USD',
'timeToPay': 1,
'description': 'Order #7, delivery',
'serviceData': 'order-7',
}, 'dvc3khto5WpjEPojZfYJsbJzFEPPVnRa')
print(container['data'])
print(container['signature'])
Argumen separators diperlukan agar sama persis dengan acuan. Penerima juga tidak masalah dengan pengaturan JSON bawaan.
Nilai contohnya ASCII, tetapi kode ini bekerja dengan Unicode apa pun tanpa perubahan. PHP dan Python secara bawaan meng-escape non-ASCII sebagai \uXXXX, JavaScript mengirim UTF-8 mentah—penerima menerima keduanya.
Dua kesalahan umum
Yang ditandatangani JSON, bukan kontainer. HMAC dihitung atas string data, yaitu atas base64url. Kesalahan ini tampak seperti “semuanya cocok, tetapi tanda tangannya tidak.”
Kontainer dibangun ulang setelah penandatanganan. Jika satu byte saja dari data berubah, tanda tangan tidak sah. Bangun pasangan ini di satu tempat dalam kode Anda.
Jika tanda tangan tidak cocok
Telusuri rantai JSON → data → tanda tangan; mata rantai pertama yang menyimpang dari acuan adalah penyebabnya. Periksa data lebih dulu: nilainya tidak bergantung pada kunci rahasia.