Skip to content

签名与加解密 ​

本页说明平台接口的签名机制(RSA2 / SM2)、contentHash 计算规则与响应加解密模式,并提供 PHP 签名示例

支持的签名算法 ​

算法类型适用版本推荐程度说明
RSA21.0推荐SHA256WithRSA,从应用读取密钥
SM21.0推荐国密 SM2,从应用读取密钥

INFO

版本与签名算法对应关系:

  • 1.0 版本:支持 RSA2 与 SM2 两种签名方式,均从应用(appid)读取密钥及加密方式

签名基本规则 ​

  • 参数排序:将数据内的非空参数值的参数按照参数名 ASCII 码从小到大排序(字典序)
  • 空值处理:假设数据为空,则此字段不加入签名
  • bizContent 处理:传送的 bizContent 字段请加入签名前做排序处理
  • 数据格式:bizContent 应为 String 数据,如 PHP 方式为 json_encode
  • 原文签名:所有参数加入签名时为原报文内容(不剔除空格及特殊字符)进行签名

交互方式: POST: application/x-www-form-urlencoded;charset=utf-8

请求签名(RSA2 / SM2) ​

1.0 版本支持 RSA2 与 SM2 两种签名方式,密钥及加密方式均从应用(appid)读取。

参与签名的公共参数 ​

参数名必填说明
appIdY平台应用ID
subMerchantNoY子商户编号
bizContentY业务参数(JSON 字符串)
versionN版本号(可选,传了才参与签名)
nonceY随机字符串(32位十六进制)
contentHashYbizContent 的 SHA256 哈希值
timestampY时间戳(yyyy-MM-dd HH:mm:ss)

INFO

注意: sign 参数本身不参与签名;version 为可选参数,传递时才参与签名。

签名内容 ​

对上述公共请求参数按 key 字典序排序后,JSON 序列化(JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),对该字符串签名。

签名类型签名方式输出格式
RSA2商户私钥签名,SHA256base64
SM2商户 SM2 私钥签名base64

contentHash 计算 ​

php
// 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版本) ​

TIP

RSA2 签名方式,从应用(appid)读取商户密钥,使用前需先创建应用。

密钥配置说明 ​

密钥类型来源用途
商户 RSA2 私钥从应用列表一键生成请求签名
平台 RSA2 公钥从应用详情获取平台RSA2公钥验签平台响应

签名流程 ​

  1. 生成密钥:使用支付宝工具生成 RSA2#PKCS8 密钥,上传公钥至商户后台
  2. 参数排序:将所有参数按 ASCII 码排序
  3. JSON 序列化:将排序后的参数转为 JSON 字符串
  4. RSA2 签名:使用商户私钥对 JSON 字符串进行 SHA256WithRSA 签名
  5. Base64 编码:将签名结果进行 Base64 编码

PHP 签名示例 ​

php
// PHP RSA2 签名示例
$bizContent = ['paymentMethod' => 'HUIFU_WX_MINIAPP', 'orderNo' => 'TEST20260701', 'amount' => '0.01'];
ksort($bizContent);
$bizContentStr = json_encode($bizContent, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

$param = [
    'appId'       => '平台应用ID',
    'subMerchantNo' => '子商户编号',
    '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);

// RSA2 签名
openssl_sign($json, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$param['sign'] = base64_encode($signature);

SM2 签名算法(1.0版本) ​

TIP

SM2 签名方式,从应用(appid)读取商户密钥,使用前需先创建应用。

密钥配置说明 ​

密钥类型来源用途
商户 SM2 私钥从应用列表一键生成请求签名
平台 SM2 公钥从应用详情获取平台SM2公钥验签平台响应

签名步骤说明 ​

以下是 SM2 签名的通用逻辑,适用于所有编程语言:

步骤操作详细说明
1构建业务参数将业务参数按 ASCII 码排序,转为 JSON 字符串作为 bizContent
2计算 contentHash对 bizContent 进行 SHA256 哈希,生成 64 位十六进制字符串
3构建请求参数添加 appId、subMerchantNo、bizContent、version、nonce、contentHash、timestamp 等参数
4参数排序将所有参数按 ASCII 码升序排列
5JSON 序列化将排序后的参数转为 JSON 字符串(不转义中文、不转义斜杠)
6SM2 签名使用商户 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 签名示例 ​

INFO

依赖说明: 示例代码使用封装好的 Sm2Helper 类,基于 lpilp/guomi: ^2.0 库实现。

php
// 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',
    'subMerchantNo' => '子商户编号',
    '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 类参考实现 ​

TIP

以下是 Sm2Helper 类的完整实现,仅供参考。

php
<?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();
        $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();
        $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));
    }
}

加解密规则 ​

公共参数 encrypt_response ​

参数类型必填默认值说明
encrypt_responsestring否11=返回加密,需解密;0=返回明文+签名,客户端验签即可

两种模式对比 ​

encrypt_response响应形式客户端操作
1成功时返回加密数据(request_array 内为 encrypted_data、signature、sign_type)① 用平台公钥验签 ② 用商户私钥解密
0成功时返回明文 + 顶层 sign、sign_type用平台公钥验签,无需解密

选择建议 ​

TIP

推荐 encrypt_response=0: 请求时传 encrypt_response=0,成功响应为明文 + sign、sign_type,客户端按文档构建 payload 用平台公钥验签即可,实现简单。

INFO

encrypt_response=1(需加密传输): 请求时传 1 或不传(默认 1),成功时 request_array 内为 encrypted_data、signature、sign_type,需用平台公钥验签、商户私钥解密。

详细验签与解密流程请参考「签名规则」。