Skip to content

签名与验签算法

另见:开发测试配置参数

签名&验签通用步骤

  1. 设所有发送或者接收到的数据为集合M
  2. 将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序)
  3. 使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA

签名&验签

  • 使用商户私钥对stringA签名 得到 sign = RSAUtils.createSign(stringA,‘商户私钥’)
  • 使用平台公钥对stringA验签 得到 isPass = RSAUtils.verifySign(stringA,‘平台公钥’)

特别注意以下重要规则(必看):

  1. 参数名ASCII码从小到大排序(字典序);
  2. 如果参数的值为空(如空字符串,0,0.0,null,false,字符串'0')不参与签名;
  3. 签名原始串(stringA)中,参数名和参数值均采用原始值,不进行URL Encode;
  4. 签名算法: OPENSSL_ALGO_SHA256(商户私钥加签,平台公钥验签)
  5. 请求参数小数点后面的零请去除,如101.00转成101,11.20转为11.2,13.33无需调整
  6. 不是进行rsa加密,是创建rsa签名

参数参数排序示例

json
{
  "appId": 10001,
  "mchOrderNo": "order_1662098266",
  "bankCode": "CLABE",
  "amount": 1200,
  "notifyUrl": "http://www.domain.com/notify",
  "name": "Gary Williams",
  "phone": "88123456789",
  "email": "test@gmail.com",
  "currentTime": 1662098266,
  "sign": "AFEleaq1Hpr3jzEqy7rnDhZyrB8JfR2380sU2aTs4jA82ULV/eRuieoO9STzVLSM+wr/J/+2x30rWvNJ+xYLe99TZBVwHtzlU+p7+1ie4mq8YNfhUlXGiWPI/9x7AuHmRH5iCP0+kgnhqWk7h+5ye+s330nKC+BUPlGlCaCaBnY=",
  "payer": {
    "name": "xxxx",
    "cpf": "xxxdsahfu"
  }
}

排序后的stringA为

amount=1200&appId=10001&bankCode=CLABE&currentTime=1662098266&email=test@gmail.com&mchOrderNo=order_1662098266&name=Gary Williams&notifyUrl=http://www.domain.com/notify&phone=88123456789&payer=Array

签名示例 Java签名DEMOGolang签名DEMONodejs签名DEMO

PHP签名示例

php
class SignUtils
{
    /**
     * 私钥创建签名
     * @param array $params
     * @param string $privateKey
     * @return string
     */
    public static function createSign(array $params, string $privateKey): string
    {
        $signString = self::getSignString($params);
        $privateKey = '-----BEGIN PRIVATE KEY-----' . "\n" . $privateKey . "\n" . '-----END PRIVATE KEY-----';
        //转换为openssl密钥,必须是没有经过pkcs8转换的私钥
        $res = openssl_get_privatekey($privateKey);
        //调用openssl内置签名方法,生成签名$sign
        openssl_sign($signString, $sign, $res, OPENSSL_ALGO_SHA256);
        //释放资源
        openssl_free_key($res);
        return base64_encode($sign);
    }

    /**
     * 公钥验证签名
     * @param array $params
     * @param string $pubKey
     * @return bool
     */
    public static function verifySign(array $params, string $pubKey): bool
    {
        $sign = $params['sign'] ?? '';
        $signString = self::getSignString($params);
        $pubKey = '-----BEGIN PUBLIC KEY-----' . "\n" . $pubKey . "\n" . '-----END PUBLIC KEY-----';
        //转换为openssl格式密钥
        $res = openssl_get_publickey($pubKey);
        //调用openssl内置方法验签,返回bool值
        $result = (bool)openssl_verify($signString, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
        //释放资源
        openssl_free_key($res);
        return $result;
    }

    /**
     * @note 获取预处理字符串
     * @param array $params
     * @return string
     */
    private static function getSignString(array $params): string
    {
        unset($params['sign']);
        ksort($params);
        $string = '';
        if (!empty($params)) {
            $array = array();
            foreach ($params as $key => $value) {
                if (!empty($value)) {
                    $array[] = $key . '=' . $value;
                }
            }
            $string = implode("&", $array);
        }
        return $string;
    }
}

Python示例

python
import base64
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256

## 依赖 pycryptodome

class SignUtils:
    @staticmethod
    def create_sign(params, private_key):
        sign_string = get_sign_string(params)
        private_key = f'-----BEGIN PRIVATE KEY-----
{private_key}
-----END PRIVATE KEY-----'
        res = RSA.import_key(private_key)
        sign = PKCS1_v1_5.new(res).sign(SHA256.new(sign_string.encode()))
        return base64.b64encode(sign).decode()

    @staticmethod
    def verify_sign(params, pub_key):
        sign = params.get('sign')
        sign_string = get_sign_string(params)
        pub_key = f'-----BEGIN PUBLIC KEY-----
{pub_key}
-----END PUBLIC KEY-----'
        res = RSA.import_key(pub_key)
        try:
            PKCS1_v1_5.new(res).verify(SHA256.new(sign_string.encode()), base64.b64decode(sign))
            return True
        except (ValueError, TypeError):
            return False

    @staticmethod
    def get_sign_string(params):
        sorted_params = sorted(params.items(), key=lambda x: x[0])
        array = []
        for key, value in sorted_params:
            if value:
                array.append(f"{key}={value}")
        return '&'.join(array)

Transafe 开发者文档