签名算法说明
所有接口统一使用 HMAC‑SHA256 进行签名,用于身份校验与防篡改、防重放。签名密钥为 openapi_secret,该密钥仅参与签名计算,不随请求传递。
签名流程步骤
- 收集全部请求参数:包含鉴权参数(openapi_key、Timestamp、Nonce)+ 当前接口所有业务表单参数;Signature 参数本身不参与签名拼接
- 对参数名按照字典序(ASCII升序)排序;
- 按顺序拼接成
key=value 形式,多个参数直接相连,无分隔符;
示例:openapi_key=AK123Nonce=xxxTimestamp=1769932800DomainName=push.xxx.com
- 使用 openapi_secret 作为密钥,对上面拼接好的原始串做 HMAC‑SHA256;
- 将得到的二进制结果转为小写十六进制字符串,即为 Signature;
- 最后把 Signature 和其他参数一起 POST 提交到接口。
⚠️ 重点规则:
1. 参数值不做URL编码再参与签名(前后端统一规则);
2. 参数值为中文、特殊字符直接原样参与拼接;
3. 时间戳为Unix秒级时间戳;
4. 密钥 openapi_secret 不要传给接口,仅本地用来算签名;
5. 排序是参数名排序,不是参数值排序。
示例演示(用于测试)
测试数据:
openapi_key = "AK001"
openapi_secret = "SKabc123456"
Timestamp = "1769932800"
Nonce = "550e8400-e29b-41d4-a716-446655440000"
业务参数:DomainName="push.chunyuyun.cn",AppName="live",StreamName="test001"排序后的 key 顺序:AppName,DomainName,Nonce,StreamName,Timestamp,openapi_key
拼接原文:
AppName=liveDomainName=push.chunyuyun.cnNonce=550e8400-e29b-41d4-a716-446655440000StreamName=test001Timestamp=1769932800openapi_key=AK001
PHP 签名示例
<?php
function calcSign($params, $secret)
{
// 移除Signature,不参与签名
unset($params['Signature']);
// 按键名字典排序
ksort($params, SORT_ASC);
$src = '';
foreach ($params as $k => $v) {
$src .= $k . '=' . $v;
}
// HMAC-SHA256,转小写16进制
$sign = hash_hmac('sha256', $src, $secret, false);
return strtolower($sign);
}// 使用
$secret = "SKabc123456";
$param = [
"openapi_key" => "AK001",
"Timestamp" => "1769932800",
"Nonce" => "550e8400-e29b-41d4-a716-446655440000",
"DomainName" => "[push.chunyuyun.cn](https://push.chunyuyun.cn)",
"AppName" => "live",
"StreamName" => "test001"
];
\(sign = calcSign(\)param, $secret);
echo $sign;
?>
Python 签名示例(Python3)
import hmac
import hashlibdef calc_sign(params: dict, secret: str) -> str:
params = params.copy()
params.pop("Signature", None)
# 按键排序
sorted_items = sorted(params.items(), key=lambda x: x[0])
src = "".join([f"{k}={v}" for k, v in sorted_items])
h = hmac.new(secret.encode("utf-8"), src.encode("utf-8"), hashlib.sha256)
return h.hexdigest().lower()
# 使用
secret = "SKabc123456"
param = {
"openapi_key": "AK001",
"Timestamp": "1769932800",
"Nonce": "550e8400-e29b-41d4-a716-446655440000",
"DomainName": "[push.chunyuyun.cn](https://push.chunyuyun.cn)",
"AppName": "live",
"StreamName": "test001"
}
sign = calc_sign(param, secret)
print(sign)
Golang 签名示例
package mainimport (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
)
func CalcSign (params map [string] string, secret string) string {
delete (params, "Signature")
// 获取 key 并排序
var keys [] string
for k := range params {
keys = append (keys, k)
}
sort.Strings (keys)
var src string
for _, k := range keys {
src += k + "=" + params [k]
}
h := hmac.New (sha256.New, [] byte (secret))
h.Write ([] byte (src))
return hex.EncodeToString (h.Sum (nil))
}
func main() {
secret := "SKabc123456"
param := map[string]string{
"openapi_key": "AK001",
"Timestamp": "1769932800",
"Nonce": "550e8400-e29b-41d4-a716-446655440000",
"DomainName": "[push.chunyuyun.cn](https://push.chunyuyun.cn)",
"AppName": "live",
"StreamName": "test001",
}
sign := CalcSign(param, secret)
fmt.Println(sign)
}
C# (.NET Framework 4.7.2) 签名示例
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;public static string CalcSign(SortedDictionary paramDic, string secret)
{
if (paramDic.ContainsKey("Signature"))
paramDic.Remove("Signature");
StringBuilder sb = new StringBuilder();
foreach (var kv in paramDic)
{
sb.Append($"{kv.Key}={kv.Value}");
}
string src = sb.ToString();
byte[] keyBytes = Encoding.UTF8.GetBytes(secret);
byte[] srcBytes = Encoding.UTF8.GetBytes(src);
using (HMACSHA256 hmac = new HMACSHA256(keyBytes))
{
byte[] hash = hmac.ComputeHash(srcBytes);
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
// 调用示例
/*
var dic = new SortedDictionary
{
{"openapi_key","AK001"},
{"Timestamp","1769932800"},
{"Nonce","550e8400-e29b-41d4-a716-446655440000"},
{"DomainName","[push.chunyuyun.cn](https://push.chunyuyun.cn)"},
{"AppName","live"},
{"StreamName","test001"}
};
string sign = CalcSign(dic,"SKabc123456");
*/
Java 签名示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;public class SignUtil {
public static String calcSign (Map params, String secret) throws Exception {
params.remove ("Signature");
// TreeMap 天然字典升序
TreeMap sortedMap = new TreeMap<>(params);
StringBuilder src = new StringBuilder ();
for (Map.Entry entry : sortedMap.entrySet ()) {
src.append (entry.getKey ()).append ("=").append (entry.getValue ());
}
SecretKeySpec keySpec = new SecretKeySpec (secret.getBytes (StandardCharsets.UTF_8), "HmacSHA256");
Mac mac = Mac.getInstance ("HmacSHA256");
mac.init (keySpec);
byte [] result = mac.doFinal (src.toString ().getBytes (StandardCharsets.UTF_8));
StringBuilder hexStr = new StringBuilder ();
for (byte b : result) {
String hex = Integer.toHexString (b & 0xFF);
if (hex.length () == 1) hexStr.append ("0");
hexStr.append (hex);
}
return hexStr.toString ().toLowerCase ();
}
public static void main(String[] args) throws Exception {
String secret = "SKabc123456";
Map param = new HashMap<>();
param.put("openapi_key", "AK001");
param.put("Timestamp", "1769932800");
param.put("Nonce", "550e8400-e29b-41d4-a716-446655440000");
param.put("DomainName", "[push.chunyuyun.cn](https://push.chunyuyun.cn)");
param.put("AppName", "live");
param.put("StreamName", "test001");
System.out.println(calcSign(param, secret));
}
}
请求组装说明
算好签名之后,把 Signature 和所有参数一起放入 Form 表单 POST 请求。
POST /接口名称.ashx
Form表单参数:
openapi_key=AK001
Timestamp=1769932800
Nonce=550e8400-e29b-41d4-a716-446655440000
DomainName=push.chunyuyun.cn
AppName=live
StreamName=test001
Signature=【上面代码计算得到的签名字符串】
排查签名不一致最常见坑:
1. 排序错误:是参数名排序,不是值;
2. Signature 参与了签名原文拼接;
3. 一边编码urlencode,另一边没有编码;
4. 大小写问题:最终签名要求小写;
5. 编码不一致,一个UTF8一个GBK;
6. 前后时间戳不一致,时差超过±300s。