跳到主要内容

HTML 表单签名

买家可以直接在浏览器中改动表单的任何字段——例如把金额改小。因此收款单字段放在签名容器中传输:服务会重新计算签名,不相符就不创建收款单。

签名由店铺服务器计算。发送到浏览器的只有算好的 datasignature 这对值。

签名的对象

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

HMAC 基于 data 字符串计算,而不是 JSON 的字节。接收端先按提交的字符串原样验证签名,之后才解码。

由此可知没有规范化处理:键顺序、JSON 缩进和 Unicode 转义风格都不影响签名。任何标准 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 字符串和签名都会不同。

如果 JSON 看起来相同而 data 不一致,原因在 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";

示例值都是 ASCII,但代码无需改动即可处理任何 Unicode。PHP 和 Python 默认把非 ASCII 转义为 \uXXXX,JavaScript 发送原始 UTF-8——接收端两者都接受。

两个常见错误

签的是 JSON 而不是容器。HMAC 基于 data 字符串,也就是 base64url 计算。这种错误的表现是“一切都对,唯独签名不符”。

签名后重新生成了容器data 哪怕只变动一个字节,签名就会失效。在代码中的同一处生成这对值。

签名不符时

沿着 JSON → data → 签名的链条逐环排查;最先与参考值出现分歧的环节就是原因。先检查 data:它不依赖密钥。