签名规则
支持的签名算法
| 算法类型 | 适用版本 | 推荐程度 | 说明 |
|---|---|---|---|
| RSA2 | 1.0 | 推荐 | SHA256WithRSA,从应用读取密钥 |
| SM2 | 1.0 | 推荐 | 国密 SM2,从应用读取密钥 |
版本与签名算法对应关系:
- 1.0 版本:支持 RSA2 与 SM2 两种签名方式,均从应用(appid)读取密钥及加密方式
签名基本规则
- 参数排序:将数据内的非空参数值的参数按照参数名 ASCII 码从小到大排序(字典序)
- 空值处理:假设数据为空,则此字段不加入签名
- bizContent 处理:传送的 bizContent 字段请加入签名前做排序处理
- 数据格式:bizContent 应为 String 数据,如 PHP 方式为 json_encode
- 原文签名:所有参数加入签名时为原报文内容(不剔除空格及特殊字符)进行签名
交互方式:POST: application/x-www-form-urlencoded;charset=utf-8
3.3 请求签名(RSA2 / SM2)
1.0 版本支持 RSA2 与 SM2 两种签名方式,密钥及加密方式均从应用(appid)读取。
签名内容
对所有公共请求参数(appId、subMerchantNo、bizContent、version、nonce、contentHash、timestamp)按 key 字典序排序后,JSON 序列化(JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),对该字符串签名。
| 签名类型 | 签名方式 | 输出格式 |
|---|---|---|
| RSA2 | 商户私钥签名,SHA256 | base64 |
| SM2 | 商户 SM2 私钥签名 | base64 |
3.4 contentHash 计算
// bizContent 规范
$bizContentNorm = preg_replace('/[\\x00-\\x1F\\x7F]/u', '', html_entity_decode($bizContent));
$contentHash = hash('sha256', $bizContentNorm);
bizContent 规范:去除控制字符(0x00-0x1F、0x7F),并对 HTML 实体解码后的字符串取 SHA256。
RSA2 签名算法(1.0版本)
RSA2 签名方式,从应用(appid)读取商户密钥,使用前需先创建应用。。
密钥配置说明
| 密钥类型 | 来源 | 用途 |
|---|---|---|
| 商户 RSA2 私钥 | 从应用列表一键生成 | 请求签名; |
| 平台 RSA2 公钥 | 从应用详情获取平台RSA2公钥 | 验签平台响应 |
签名流程
- 生成密钥:使用支付宝工具生成 RSA2#PKCS8 密钥,上传公钥至商户后台
- 参数排序:将所有参数按 ASCII 码排序
- JSON 序列化:将排序后的参数转为 JSON 字符串
- RSA2 签名:使用商户私钥对 JSON 字符串进行 SHA256WithRSA 签名
- Base64 编码:将签名结果进行 Base64 编码
// PHP RSA2 签名示例
function generateSign($params, $private_key) {
unset($params['sign']);
ksort($params); // 参数排序
// 注意:必须同时使用 JSON_UNESCAPED_UNICODE 和 JSON_UNESCAPED_SLASHES
$json_data = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
openssl_sign($json_data, $signature, $private_key, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
SM2 签名算法(1.0版本)
SM2 签名方式,从应用(appid)读取商户密钥,使用前需先创建应用。。
密钥配置说明
| 密钥类型 | 来源 | 用途 |
|---|---|---|
| 商户 SM2 私钥 | 从应用列表一键生成 | 请求签名; |
| 平台 SM2 公钥 | 从应用详情获取平台SM2公钥 | 验签平台响应 |
签名步骤说明
以下是 SM2 签名的通用逻辑,适用于所有编程语言:
| 步骤 | 操作 | 详细说明 |
|---|---|---|
| 1 | 构建业务参数 | 将业务参数按 ASCII 码排序,转为 JSON 字符串作为 bizContent |
| 2 | 计算 contentHash | 对 bizContent 进行 SHA256 哈希,生成 64 位十六进制字符串 |
| 3 | 构建请求参数 | 添加 appId、userId、bizContent、version、nonce、contentHash、timestamp 等参数 |
| 4 | 参数排序 | 将所有参数按 ASCII 码升序排列 |
| 5 | JSON 序列化 | 将排序后的参数转为 JSON 字符串(不转义中文、不转义斜杠) |
| 6 | SM2 签名 | 使用商户 SM2 私钥对 JSON 字符串进行签名,输出 Base64 格式 |
| 7 | 添加签名参数 | 将签名结果作为 sign 参数加入请求 |
关键点说明
- timestamp:格式为 yyyy-MM-dd HH:mm:ss,与服务器时间差距不能超过 5 分钟
- nonce:随机字符串(32位十六进制),用于防止重放攻击
- contentHash:对 bizContent 进行 SHA256 哈希,用于防篡改校验
- 参数排序:签名前必须对所有参数按 ASCII 码升序排列
- JSON 编码:使用不转义的 JSON 编码(JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
- SM2 签名:使用商户 SM2 私钥对 JSON 字符串进行签名,输出 Base64 格式
PHP 签名示例
依赖说明:示例代码使用封装好的
Sm2Helper 类,基于 lpilp/guomi: ^2.0 库实现。
// PHP SM2 签名示例
$bizContent = ['channel_type' => 'WECHAT', 'total_fee' => '100.00'];
ksort($bizContent);
$bizContentStr = json_encode($bizContent, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$param = [
'appId' => '平台应用ID',
'userId' => '用户账户编号',
'bizContent' => $bizContentStr,
'version' => '1.0',
'nonce' => bin2hex(random_bytes(16)),
'contentHash' => hash('sha256', $bizContentStr),
'timestamp' => date('Y-m-d H:i:s'),
];
ksort($param);
$json = json_encode($param, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$param['sign'] = Sm2Helper::sign($json, $merchantPrivateKey);
Sm2Helper 类参考实现
以下是 Sm2Helper 类的完整实现,仅供参考。
<?php
namespace app\\common\\service;
use Rtgm\\sm\\RtSm2;
use Exception;
/**
* SM2加密解密辅助类
* 依赖:composer require lpilp/guomi:^2.0
*/
class Sm2Helper
{
protected static $sm2Instance = null;
protected static function getInstance()
{
if (self::$sm2Instance === null) {
self::$sm2Instance = new RtSm2();
}
return self::$sm2Instance;
}
/**
* 生成SM2密钥对
*/
public static function generateKeyPair(): array
{
$sm2 = self::getInstance();
list($privateKey, $publicKey) = $sm2->generatekey();
return [
'privateKey' => strtoupper($privateKey),
'publicKey' => strtoupper($publicKey)
];
}
/**
* SM2签名
*/
public static function sign(string $data, string $privateKey): string
{
$sm2 = self::getInstance();
// doSign 返回十六进制,转换为 Base64
$hexSignature = $sm2->doSign($data, strtolower($privateKey));
return base64_encode(hex2bin($hexSignature));
}
/**
* SM2验证签名
*/
public static function verify(string $data, string $signature, string $publicKey): bool
{
$sm2 = self::getInstance();
// 将 Base64 签名转换为十六进制
$hexSignature = bin2hex(base64_decode($signature));
return $sm2->verifySign($data, $hexSignature, strtolower($publicKey));
}
/**
* SM2加密
*/
public static function encrypt(string $data, string $publicKey): string
{
$sm2 = self::getInstance();
$encrypted = $sm2->doEncrypt($data, strtolower($publicKey));
return strtoupper($encrypted);
}
/**
* SM2解密
*/
public static function decrypt(string $encryptedData, string $privateKey): string
{
$sm2 = self::getInstance();
return $sm2->doDecrypt(strtolower($encryptedData), strtolower($privateKey));
}
}