Appearance
签名
KeepSDK 与游戏之间只用这一套签名。
算法
- 算法:MD5
- 输出:32 位小写 hex
time:10 位 Unix 秒,允许误差 ±300 秒
步骤:
- 取出本次请求里的全部参数,去掉
sign - 按参数名 ASCII 升序拼成
k=v&k=v - 值用解码后的原文,不要 urlencode 后再签
- 末尾直接拼密钥,中间没有
&key= - 对整串做 MD5
text
sign = md5( k1=v1&k2=v2&... + 密钥 )GET(登录 302、/pay)和 JSON(发货、查角色)规则相同,都签语义值。
中文、空格写进 URL 时要 urlencode;算签名仍用原文。首充8元 签原文,URL 写成 %E9%A6%96...。签 encode 之后的串会 1002。
JSON 里直接写中文即可。空值不要写进 JSON,写了就要进签。
各接口用哪把密钥
创建游戏时下发 login_key、pay_key,只展示一次。接口与密钥对应如下。
| 接口 | 方向 | 密钥 |
|---|---|---|
登录 login_url | KeepSDK → 游戏 | login_key |
查角色 role_url | KeepSDK → 游戏 | login_key |
拉区服 server_url | KeepSDK → 游戏 | login_key |
封禁 ban_url | KeepSDK → 游戏 | login_key |
聊天 POST /game/chat | 游戏 → KeepSDK | login_key |
打点 POST /game/report | 游戏 → KeepSDK | login_key |
平台VIP信息 POST /game/vip/profile | 游戏 → KeepSDK | login_key |
平台VIP收益 POST /game/vip/benefit | 游戏 → KeepSDK | login_key |
下单 GET /pay | 游戏 → KeepSDK | pay_key |
发货 deliver_url | KeepSDK → 游戏 | pay_key |
礼包 gift_url | KeepSDK → 游戏 | pay_key |
代码示例
去掉 sign 后按参数名 ASCII 升序拼 k=v&k=v,末尾直接拼密钥,没有 &key=、没有 secret=。值用原文(含中文)。拷贝后先跑文末自测样例。接口入口见 PHP 示例。
Go
go
package keepsdksign
import (
"crypto/md5"
"encoding/hex"
"fmt"
"sort"
"strings"
)
func Sign(params map[string]string, key string) string {
keys := make([]string, 0, len(params))
for k := range params {
if k == "sign" {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, fmt.Sprintf("%s=%s", k, params[k]))
}
sum := md5.Sum([]byte(strings.Join(parts, "&") + key))
return hex.EncodeToString(sum[:])
}
func SignSample() string {
return Sign(map[string]string{
"app_id": "lmyxweb",
"time": "1757250000",
"uid": "u1",
}, "test_login_key")
}JavaScript(Node.js)
js
import crypto from 'node:crypto'
export function keepsdkSign(params, key) {
const keys = Object.keys(params)
.filter((k) => k !== 'sign')
.sort()
const raw = keys.map((k) => `${k}=${params[k]}`).join('&') + key
return crypto.createHash('md5').update(raw, 'utf8').digest('hex')
}
export function keepsdkSignSample() {
return keepsdkSign(
{
app_id: 'lmyxweb',
time: '1757250000',
uid: 'u1',
},
'test_login_key',
)
}Java
java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class KeepSDKSign {
public static String sign(Map<String, String> params, String key) throws Exception {
List<String> keys = new ArrayList<String>();
for (String k : params.keySet()) {
if (!"sign".equals(k)) {
keys.add(k);
}
}
Collections.sort(keys);
StringBuilder raw = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
if (i > 0) {
raw.append('&');
}
String k = keys.get(i);
raw.append(k).append('=').append(params.get(k));
}
raw.append(key);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(raw.toString().getBytes(StandardCharsets.UTF_8));
StringBuilder out = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
out.append(String.format("%02x", b));
}
return out.toString();
}
public static String signSample() throws Exception {
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", "lmyxweb");
params.put("time", "1757250000");
params.put("uid", "u1");
return sign(params, "test_login_key");
}
}PHP
php
<?php
function keepsdk_sign($params, $key) {
unset($params['sign']);
ksort($params, SORT_STRING);
$parts = array();
foreach ($params as $k => $v) {
$parts[] = $k . '=' . $v;
}
return md5(implode('&', $parts) . $key);
}
function keepsdk_sign_sample() {
return keepsdk_sign(array(
'app_id' => 'lmyxweb',
'time' => '1757250000',
'uid' => 'u1',
), 'test_login_key');
}Python
python
import hashlib
def keepsdk_sign(params: dict, key: str) -> str:
keys = sorted(k for k in params if k != "sign")
raw = "&".join(f"{k}={params[k]}" for k in keys) + key
return hashlib.md5(raw.encode("utf-8")).hexdigest()
def keepsdk_sign_sample() -> str:
return keepsdk_sign(
{
"app_id": "lmyxweb",
"time": "1757250000",
"uid": "u1",
},
"test_login_key",
)C#
csharp
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public static class KeepSDKSign
{
public static string Sign(Dictionary<string, string> paramsMap, string key)
{
var keys = paramsMap.Keys
.Where(k => k != "sign")
.OrderBy(k => k, StringComparer.Ordinal)
.ToList();
var raw = new StringBuilder();
for (var i = 0; i < keys.Count; i++)
{
if (i > 0) raw.Append('&');
var k = keys[i];
raw.Append(k).Append('=').Append(paramsMap[k]);
}
raw.Append(key);
using var md5 = MD5.Create();
var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(raw.ToString()));
var sign = new StringBuilder(hash.Length * 2);
foreach (var b in hash)
{
sign.Append(b.ToString("x2"));
}
return sign.ToString();
}
public static string SignSample()
{
var paramsMap = new Dictionary<string, string>
{
{ "app_id", "lmyxweb" },
{ "time", "1757250000" },
{ "uid", "u1" },
};
return Sign(paramsMap, "test_login_key");
}
}自测样例
写完后先用这一组对一下:先对 raw,再对 sign。密钥是直接拼在串尾,前面没有 &。
text
params:
app_id=lmyxweb
time=1757250000
uid=u1
key=test_login_key
raw:
app_id=lmyxweb&time=1757250000&uid=u1test_login_key
sign:
28983fd71d2d477de66244003981f475