精彩博文

NG Gaming精彩博文,分享游戏API接口、NG包网、包网平台、包网系统及游戏平台运营等行业资讯,提供专业的游戏行业知识与解决方案。

游戏 API MD5 签名生成指南:附 PHP、Java、Go、Node.js 完整代码示例

2026-09-29 精彩博文 32


在搭建游戏平台、接入第三方游戏 API 或进行NG接口对接时,API 签名与验签机制(Signature Verification)是保证系统通信安全、防数据篡改和防重放攻击的核心生命线。

本文将标准化拆解游戏 API 签名生成的核心流程,梳理常见的“踩坑”排查点,并提供 PHP、Java、Go、Node.js 等主流语言的完整代码示例,帮助开发工程师快速完成无缝对接。



一、签名计算标准化五步法

为了确保不同开发语言环境(如客户端、服务端与第三方网关)计算出的签名完全一致,业界通用采用以下标准化五步算法:

  1. 收集有效请求参数:获取所有需要参与签名的参数(包括请求体和 query 参数),自动剔除 sign 或 signature 字段本身。

  2. 字典序排序(ASCII 码):将过滤后的参数名按 ASCII 码升序排序(A-Z,a-z)。

  3. 拼接 key=value 字符串:按照排序后的顺序,将参数拼装为 k1=v1&k2=v2 的格式。

  4. 拼接私钥(SecretKey):在拼接好的字符串末尾追加由平台分配的通信私钥(如 &key=YOUR_SECRET_KEY)。

  5. MD5 哈希计算:对最终字符串进行 MD5 计算,并将结果统一转换为大写或小写(以接口文档规定为准)。

二、开发过程中常见的踩坑与避坑指南

在实际对接NG接口或其他游戏 API 时,签名校验失败通常由以下三类细节导致:

1. null 与空字符串 "" 的过滤规则

  • 坑点:部分语言会将 null 隐式转换为 "",导致拼接字符串长度不一致。

  • 规则:通常签名算法要求直接忽略值为 null 或空字符串 "" 的键值对,不参与排序和拼接。

2. 参数名大小写敏感性

  • 坑点:ASCII 码中大写字母(A-Z: 65-90)优先于小写字母(a-z: 97-122)。

  • 规则:进行字典序排序时,切勿统一转为大写或小写再排序,需直接基于原始字符串的 ASCII 值排列。

3. URL Encode(特殊字符编码)导致的签名失败

  • 坑点:网络传输时部分框架会对参数进行 URL 编码(如空格变 %20 或 +,斜杠被转义)。

  • 规则:计算签名时必须使用原始未编码的参数值,仅在最终 HTTP 请求传输时才执行 URL Encode。

三、多语言完整 Code Snippet

1. PHP 完整实现

PHP
<?php/**
 * 生成游戏 API MD5 签名
 */function generateSign(array $params, string$secretKey): string {    // 1. 过滤 sign 与空值
    unset($params['sign'], $params['signature']);$filtered = array_filter($params, function($v) 
    {        return $v !== null &&$v !== '';
    });    // 2. ASCII 字典序排序
    ksort($filtered);    // 3. 拼接 key=value
    $pairs = [];    foreach ($filtered as$k => $v) {$pairs[] = $k . '=' .$v;
    }    $queryString = implode('&',$pairs);    // 4. 末尾拼接 SecretKey
    $rawString = $queryString . '&key=' .$secretKey;    // 5. MD5 转换大写
    return strtoupper(md5($rawString));
}// 单元测试$testData = [     'app_id'    => 'game_1001',     'player_id' => 'user_9999',     'amount'    =>

 '100.00',     'timestamp' => '1727395200',     'empty_val' => '' ];$secret = 'MySecret2026';echo "PHP Sign: " . generateSign($testData,$secret);

2. Java (Spring Boot) 完整实现

Java
import java.nio.charset.StandardCharsets;import java.security.MessageDigest;import java.util.Map;import java.util.TreeMap;public class ApiSigner 

{    public static String generateSign(Map<String, String> params, String secretKey) throws Exception 

{        // 1 & 2. 使用 TreeMap 自动按 ASCII 字典序排序并剔除 sign
        Map<String, String> sortedMap = new TreeMap<>(params);
        sortedMap.remove("sign");
        sortedMap.remove("signature");        // 3. 拼接 key=value
        StringBuilder sb = new StringBuilder();        for (Map.Entry<String, String> entry : sortedMap.entrySet()) 
        
        {            if (entry.getValue() != null && !entry.getValue().isEmpty()) {
                sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }        // 4. 追加 SecretKey
        sb.append("key=").append(secretKey);        // 5. MD5 转大写
        MessageDigest md = MessageDigest.getInstance("MD5");        byte[] array = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
        StringBuilder hexString = new StringBuilder();        for (byte b : array) {
            hexString.append(String.format("%02X", b));
        }        return hexString.toString();
    }
}

3. Go (Golang) 完整实现

Go
package mainimport (	"crypto/md5"
	"encoding/hex"
	"fmt"
	"sort"
	"strings")func GenerateSign(params map[string]string, secretKey string) string {	// 1. 收集并排序 key
	var keys []string
	for k, v := range params {		if k == "sign" || k == "signature" || v == "" {			continue
		}
		keys = append(keys, k)
	}
	sort.Strings(keys)	// 2. 拼接字符串
	var pairs []string
	for _, k := range keys {
		pairs = append(pairs, fmt.Sprintf("%s=%s", k, params[k]))
	}
	queryString := strings.Join(pairs, "&")
	rawString := fmt.Sprintf("%s&key=%s", queryString, secretKey)	// 3. MD5 计算并转大写
	hash := md5.Sum([]byte(rawString))	return strings.ToUpper(hex.EncodeToString(hash[:]))
}

4. Node.js / TypeScript 完整实现

TypeScript
import * as crypto from 'crypto';interface Params {
    [key: string]: string | number | undefined | null;
}export function generateSign(params: Params, secretKey: string): string {    // 1 & 2. 过滤并按 ASCII 字典序排序
    const sortedKeys = Object.keys(params)
        .filter(key => key !== 'sign' && key !== 'signature' && params[key] !== null && params[key] !== '')
        .sort();    // 3. 拼接 key=value
    const queryString = sortedKeys
        .map(key => `${key}=${params[key]}`)
        .join('&');    // 4. 追加 SecretKey
    const rawString = `${queryString}&key=${secretKey}`;    // 5. MD5 计算并转大写
    return crypto
        .createHash('md5')
        .update(rawString, 'utf8')
        .digest('hex')
        .toUpperCase();
}

深入了解防篡改原理与更多验签实战细节,可参阅游戏API MD5签名怎么做?API验签机制全面解析与实战指南。


猜你喜欢