精彩博文

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

游戏 API MD5 签名算法与安全校验实战(附 PHP/Java/Go/Node.js 示例代码)

2026-10-03 精彩博文 18

在跨国与第三方游戏接口对接(如单一钱包 Seamless Wallet、玩家下注与结算回调)中,网络传输的数据面临着中间人攻击、数据篡改和伪造请求等严重安全威胁。为保证商户平台与游戏服务端之间通信的完整性与不可否认性,采用标准且高效的 API 签名验签机制是不可或缺的一环。

本文将深入解析游戏 API 对接中的 MD5 签名生成算法 与验签逻辑,并提供主流开发语言的实战代码示例,帮助研发人员快速完成安全对接。



一、 API 签名校验的核心原理

在完整的 API 签名机制中,发送方与接收方会预先约定一个不公开的私钥(Secret Key)。签名生成的基本流程如下:

  1. 剔除无效字段:过滤掉请求参数中的空值(null 或 "")以及不参与签名的字段(如 signature 本身)。

  2. 字典序排序:将剩余的所有请求参数按参数名(Key)的 ASCII 码升序(Alphabetical Order) 进行排序。

  3. 参数拼接:按照 key1=value1&key2=value2... 的格式将排序后的参数拼接成字符串。

  4. 拼接密钥:在拼接好的字符串末尾追加私钥,如 &key=YOUR_SECRET_KEY。

  5. MD5 加密与大小写转换:对最终字符串进行 MD5 哈希计算,并将得到的 32 位 MD5 值统一转换为大写(或小写,需两端一致)作为签名值 sign。

二、 多语言代码实现

在对接专业游戏服务商时,如采用NG接口提供的技术网关,服务端与商户端均需遵循一致的签名计算逻辑。以下提供 PHP、Java、Go 和 Node.js 的完整签名生成代码。

1. PHP 示例代码

PHP
<?phpfunction generateSignature(array $params, string$secretKey): string {    // 1. 过滤空值与 sign 字段
    $filteredParams = array_filter($params, function($val,$key) {        return $key !== 'sign' && $val !== '' &&$val !== null;
    }, ARRAY_FILTER_USE_BOTH);    // 2. 按 Key 进行 ASCII 字典序排序
    ksort($filteredParams);    // 3. 拼接 key=value 字符串
    $stringBuffer = [];    foreach ($filteredParams as$key => $val) {$stringBuffer[] = $key . '=' .$val;
    }    $queryString = implode('&',$stringBuffer);    // 4. 拼接 Secret Key 并计算 MD5
    $rawString = $queryString . '&key=' .$secretKey;    return strtoupper(md5($rawString));
}// 使用示例$params 
= [     'merchant_id' => '10086',     'player_id'   => 'player_vn_01',     'amount'      => '500000',     'timestamp'   => 1700000000,     'sign'        

=> 'old_sign' ];$secretKey = 'my_super_secret_key';echo generateSignature($params,$secretKey);


2. Java 示例代码

Java
import java.security.MessageDigest;import java.util.*;public class ApiSignUtil {    public static String generateSignature(Map<String, Object> params, String secretKey) throws Exception 
{        // 1. 字典序排序
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);        // 2. 拼接参数
        StringBuilder sb = new StringBuilder();        for (String key : keys) {
            Object value = params.get(key);            if ("sign".equals(key) || value == null || value.toString().isEmpty()) {                continue;
            }            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(key).append("=").append(value);
        }        // 3. 拼接 Secret Key
        sb.append("&key=").append(secretKey);        // 4. 计算 MD5 并转大写
        MessageDigest md = MessageDigest.getInstance("MD5");        byte[] bytes = md.digest(sb.toString().getBytes("UTF-8"));
        StringBuilder hexString = new StringBuilder();        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }        return hexString.toString().toUpperCase();
    }
}


3. Go 示例代码

Go
package mainimport (	"crypto/md5"
	"encoding/hex"
	"fmt"
	"sort"
	"strings")func GenerateSignature(params map[string]string, secretKey string) string {
	keys := make([]string, 0, len(params))	for k := range params {		if k != "sign" && params[k] != "" {
			keys = append(keys, k)
		}
	}	// 字典序排序
	sort.Strings(keys)	var sb strings.Builder	for _, k := range keys {		if sb.Len() > 0 {
			sb.WriteString("&")
		}
		sb.WriteString(fmt.Sprintf("%s=%s", k, params[k]))
	}

	sb.WriteString("&key=" + secretKey)	// 计算 MD5
	hash := md5.Sum([]byte(sb.String()))	return strings.ToUpper(hex.EncodeToString(hash[:]))
}


4. Node.js 示例代码

JavaScript
const crypto = require('crypto');function generateSignature(params, secretKey) {    // 1. 过滤并排序 key
    const sortedKeys = Object.keys(params)
        .filter(key => key !== 'sign' && params[key] !== '' && params[key] !== null && params[key] !== undefined)
        .sort();    // 2. 拼接 key=value
    const queryString = sortedKeys
        .map(key => `${key}=${params[key]}`)
        .join('&');    // 3. 追加 Secret Key
    const rawString = `${queryString}&key=${secretKey}`;    // 4. MD5 加密转大写
    return crypto.createHash('md5').update(rawString, 'utf8').digest('hex').toUpperCase();
}


三、 研发对接常见踩坑点与解决方案

在实际开发和排查验签失败(如返回 Invalid Signature)时,以下是 4 个最常见的技术坑点:

  1. URL Encode 编码差异

    • 问题:部分框架在拼接参数时自动进行了 urlencode(),导致空格变成 + 或 %20,破坏了原始字符串。

    • 解决:签名计算时一律使用 原始未编码字符串 进行拼接;传输时若需要 encode,需确保接收端先 decode 或基于原始参数计算签名。

  2. 大小写不匹配

    • 问题:客户端生成的 MD5 值为小写,而服务端校验规则要求为大写。

    • 解决:双方在 API 开发文档中明确约定 MD5 输出格式,建议统一使用 32 位大写 Hex。

  3. 字段类型与浮点数精度

    • 问题:JSON 传输中 amount: 500.00 在某些语言解析后变成了 500 或 500.0,导致拼接字符串不一致。

    • 解决:对于涉及金额、比率等关键数值字段,建议在接口请求中统一声明为 字符串类型(String) 或按最小货币单位(如分、VND 原生单位)传输整数。

  4. 编码格式不一致

    • 问题:字符串转 Byte 数组时使用了默认的系统编码(如 GBK),导致非 ASCII 字符(如玩家中文名、越南语字符)签名错误。

    • 解决:计算 MD5 时,全链路强制指定为 UTF-8 字符集。

四、 相关技术与架构推荐(内链架构)

为了帮助构建更健全的游戏 API 接入与安全体系,建议结合以下架构与对接方案进行整体设计:


猜你喜欢