数据签名算法
签名算法采用业内通用算法,签名生成的通用步骤如下:
设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
在stringA最后拼接上&key=密钥得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值
特别注意以下重要规则:
参数名ASCII码从小到大排序(字典序);
如果参数的值为空不参与签名;
参数名区分大小写;
接口可能增加字段,验证签名时必须支持增加的扩展字段
举例
例如传递的参数如下:
app_id: 12345
amount: 1
out_trade_no: 123456789
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下。请使用原始值,不要进行URL转义
amount=1&app_id=12345&out_trade_no=123456789
第二步:对上一步中的字符串拼接&key=密钥
amount=1&app_id=12345&out_trade_no=123456789&key=xxxxxxxxx
第三步:对上一步中字符串取MD5值
$sign = md5('amount=1&app_id=12345&out_trade_no=123456789&key=xxxxxxxxx');
第四步:对上面md5值转化为大写
$sign = strtoupper($sign);
代码示例:
// 签名方法
function sign(array $data, $key) {
ksort($data);
$sign = strtoupper(md5(urldecode(http_build_query($data)).'&key='.$key));
return $sign;
}
// 用法示例
$data = [
'app_id' => '12345',
'amount' => 1,
'out_trade_no' => '123123123123',
];
// 通信密钥
$key = 'xxxxxxxxxxx';
$sign = sign($data, $key);
php
// 签名方法
function sign(array $data, $key) {
ksort($data);
$sign = strtoupper(md5(urldecode(http_build_query($data)).'&key='.$key));
return $sign;
}
// 用法示例
$data = [
'app_id' => '12345',
'amount' => 1,
'out_trade_no' => '123123123123',
];
// 通信密钥
$key = 'xxxxxxxxxxx';
$sign = sign($data, $key);
python
# !/usr/bin/env Python3
# -*- coding: utf-8 -*-
import hashlib
from urllib.parse import urlencode,unquote
'''
签名算法
'''
# 签名算法
def sign(attributes, key):
attributes_new = {k: attributes[k] for k in sorted(attributes.keys())}
sign_str = "&".join(
[f"{key}={attributes_new[key]}" for key in attributes_new.keys()]
)
return (
hashlib.md5((sign_str + "&key=" + key).encode(encoding="utf-8"))
.hexdigest()
.upper()
)
# 用法示例
data = {
'app_id' : '12345',
'amount' : 1,
'out_trade_no' : '123123123123'
}
# 通信密钥
key = 'xxxxxxxxxxx'
sign = sign(data, key)
go
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"net/url"
"sort"
"strings"
)
// 签名算法
func sign(order map[string]string,key string)(sign string) {
data := url.Values{}
for k,v :=range order{
data.Add(k,v)
}
keys := make([]string, 0, 0)
for key := range data{
if data.Get(key) != ""{
keys = append(keys,key)
}
}
sort.Strings(keys)
body := data.Encode()
d,_ := url.QueryUnescape(body)
d += "&key=" + key
h := md5.New()
h.Write([]byte(d))
return strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
}
func main() {
// 用法示例
data := map[string]string{
"app_id":"12345",
"amount":"1",
"out_trade_no":"123123123123"}
// 通信密钥
key := "xxxxxxxxxxx"
sign := sign(data,key)
fmt.Println(sign)
}
java
package com.hello.sign;
import org.springframework.util.DigestUtils;
import java.util.*;
public class Sign_java {
//签名算法
static class sign{
String sign(Map<String, String> map,String key){
StringBuilder sb = new StringBuilder();
for(Map.Entry<String,String> entry : map.entrySet()){
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
sb.append("key=").append(key);
return DigestUtils.md5DigestAsHex(sb.toString().getBytes()).toUpperCase();
}
}
public static void main(String[] args) {
// 用法示例
Map<String, String> order = new TreeMap<String,String>();
order.put("app_id", "12345");
order.put("amount", "1");
order.put("out_trade_no", "123123123123");
order.put("... ...", "xxxxxx");
... ...
// 通信密钥
String key = "xxxxxxxxxxx";
sign s = new sign();
String sign = s.sign(order,key);
System.out.println(sign);
}
}
nodejs
const crypto = require('crypto')
// 商户号和通信密钥
const app_id = 'xxxxxxxx'
const key = 'xxxxxxxxxx'
// 排序后转换为字符串
const toQueryString = (obj) => Object.keys(obj)
.filter(key => key !== 'sign' && obj[key] !== undefined && obj[key] !== '')
.sort()
.map(key => {
if (/^http(s)?:\/\//.test(obj[key])) { return key + '=' + encodeURI(obj[key]) }
else { return key + '=' + obj[key] }
})
.join('&')
// md5
const md5 = (str, encoding = 'utf8') => crypto.createHash('md5').update(str, encoding).digest('hex')
// 构造请求数据
let params = {
'amount': 1,
'out_trade_no': '123456',
'app_id': ''
}
params = toQueryString(params)
params += '&key=' + key
// 计算出最终签名
const sign = md5(params).toUpperCase()
console.log(sign)
c
#include <iostream>
#include <string>
#include <algorithm>
#include <map>
#include "md5.cpp"
using namespace std;
string sign(map<string,string> data,string key);
string md5(string strPlain);
int main ()
{
// 用法示例
map<string,string> data = {
{ "app_id", "12345" },
{ "amount", "1" },
{ "out_trade_no", "123123123123" } };
// 通信密钥
const string key = "xxxxxxxxxxx";
string s = sign(data,key);
cout<<s<<endl;
return 0;
}
// 签名方法
string sign(map<string,string> data,const string key){
string s = "";
while (!data.empty())
{
s+=data.begin()->first+"="+data.begin()->second+"&";
data.erase(data.begin());
}
s+="key="+key;
s = md5(s);
transform(s.begin(),s.end(),s.begin(),::toupper);
return s;
}
string md5(string strPlain)
{
MD5_CTX mdContext;
int bytes;
unsigned char data[1024];
MD5Init(&mdContext);
MD5Update(&mdContext, (unsigned char*)const_cast<char*>(strPlain.c_str()), strPlain.size());
MD5Final(&mdContext);
string md5;
char buf[3];
for (int i = 0; i < 16; i++)
{
sprintf(buf, "%02x", mdContext.digest[i]);
md5.append(buf);
}
return md5;
}
ruby
# 由不愿署名的小兄弟"朋"贡献ruby签名算法
require 'rest-client'
require 'digest'
require 'json'
APP_ID = 'xxx'
APP_KEY = 'xxx'
def sign(data, key)
data = data.sort
sign_arr = data.map {|k, v| "#{k}=#{v}"}
sign_str = sign_arr.join('&') + "&key=#{key}"
Digest::MD5.hexdigest(sign_str).upcase
end
data = {
'app_id' => APP_ID,
'amount' => 101,
'out_trade_no' => '123467',
'description' => '测试商品4',
'pay_type' => 'wechat',
'notify_url' => 'http://xxx.com/pay/callback',
}
data['sign'] = sign(data, APP_KEY)
res = RestClient.post "https://open.h5zhifu.com/api/native", data.to_json, {content_type: :json, accept: :json}
p JSON.parse(res.body)
常见问题解决办法:
1、是否可以不验证异步回调通知sign:不可以,因为可能会有坏人伪造异步回调通知