Signature & Verification
See also: API Environments
Common Signature Steps
- Let all sent or received fields be set
M. - Sort non-empty parameters in
Mby parameter name in ASCII ascending order (dictionary order). - Concatenate them as URL key-value pairs (
key1=value1&key2=value2...) intostringA.
Sign & Verify
- Sign with the merchant private key:
sign = RSAUtils.createSign(stringA, merchantPrivateKey) - Verify with the platform public key:
isPass = RSAUtils.verifySign(stringA, platformPublicKey)
Important Rules
- Sort parameter names by ASCII (dictionary order).
- Empty values do not participate in signing, including empty string,
0,0.0,null,false, and string'0'. - In
stringA, both names and values use raw values (no URL encoding). - Signature algorithm: OPENSSL_ALGO_SHA256 (merchant private key signs; platform public key verifies).
- Remove trailing zeros after decimal points in request values before signing, for example
101.00→101,11.20→11.2;13.33stays unchanged. - This is RSA signing, not RSA encryption.
Parameter Sorting Example
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"
}
}Sorted stringA:
text
amount=1200&appId=10001&bankCode=CLABE¤tTime=1662098266&email=test@gmail.com&mchOrderNo=order_1662098266&name=Gary Williams¬ifyUrl=http://www.domain.com/notify&phone=88123456789&payer=ArraySignature Examples
PHP Example
php
class SignUtils
{
/**
* Create signature with private key
* @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-----';
// Convert to OpenSSL key; private key must not have been converted via PKCS#8
$res = openssl_get_privatekey($privateKey);
// Call OpenSSL signing to generate $sign
openssl_sign($signString, $sign, $res, OPENSSL_ALGO_SHA256);
// Release resources
openssl_free_key($res);
return base64_encode($sign);
}
/**
* Verify signature with public key
* @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-----';
// Convert to OpenSSL public key format
$res = openssl_get_publickey($pubKey);
// Call OpenSSL verify; returns bool
$result = (bool)openssl_verify($signString, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
// Release resources
openssl_free_key($res);
return $result;
}
/**
* @note Build the pre-sign string
* @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 Example
python
import base64
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
## Requires 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)