签名与验签算法
另见:开发测试配置参数
签名&验签通用步骤
- 设所有发送或者接收到的数据为集合M
- 将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序)
- 使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA
签名&验签
- 使用商户私钥对stringA签名 得到 sign = RSAUtils.createSign(stringA,‘商户私钥’)
- 使用平台公钥对stringA验签 得到 isPass = RSAUtils.verifySign(stringA,‘平台公钥’)
特别注意以下重要规则(必看):
- 参数名ASCII码从小到大排序(字典序);
- 如果参数的值为空(如空字符串,0,0.0,null,false,字符串'0')不参与签名;
- 签名原始串(stringA)中,参数名和参数值均采用原始值,不进行URL Encode;
- 签名算法: OPENSSL_ALGO_SHA256(商户私钥加签,平台公钥验签)
- 请求参数小数点后面的零请去除,如101.00转成101,11.20转为11.2,13.33无需调整
- 不是进行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¤tTime=1662098266&email=test@gmail.com&mchOrderNo=order_1662098266&name=Gary Williams¬ifyUrl=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)