通信协议
环境地址
- 接口地址前缀:请联系官方团队获取
接入授权
商户在接入前需要向官方申请接入授权,包含以下内容:
- 基本信息
- 接口域名
- 白名单 IP
在申请通过后,官方将发放接入授权给商户,包含以下内容:
- 商户编号 (merchant-id):唯一数字编号
- 商户授权私钥 (secret):协议加密密钥,32 位长度字符串
以上授权信息将被适用于商户服务器与官方服务器的接口交互中,商户需要妥善保管,且秘钥信息应当仅存在于服务器中。
授权信息遗失或者泄露,可以向官方申请更换,更换授权不影响已有数据(用户、资金、订单等)的使用
通信规范
加密算法
1. 概述
游戏对接 API 中使用的加密方案,采用 AES-256-CBC 模式结合 PKCS7 填充进行数据加密与解密。API 请求中,所有业务数据需要进行加密处理后通过参数 x 传递。
2. 加密算法详情
- 加密算法:AES (Advanced Encryption Standard)
- 密钥长度:256 位 (32 字节)
- 加密模式:CBC (Cipher Block Chaining)
- 填充方式:PKCS7
- 初始化向量(IV):16 字节,需与每次请求一起传递或使用约定方式生成
3. 请求格式说明
3.2 请求体格式
所有请求的 header 中需要包含以下参数:
merchant-id: 商户编号,用于标识接入的商户身份
所有请求的 body 必须包含以下公共参数后进行加密:
timestamp: int64 类型,表示当前时间戳,精确到毫秒,13 位request_id: string 类型,唯一请求 ID
这些公共参数与业务参数一起组成 JSON 对象,加密后通过单一参数x发送:
json
{
"x": "加密后的Base64字符串"
}3.2 示例流程
- 设置请求头:
TEXT
merchant-id: M202405120001- 准备原始请求数据:
json
{
"timestamp": 1650123456789,
"request_id": "abcd-1234-abcd-1234",
"username": "game001",
"user_id": "user123",
"amount": 100
}- 将原始 JSON 加密后,得到请求体:
json
{
"x": "hG7bVwMRV0Gq+Lh5HvdkE1jBJxHIQcm9z3r4XBY/5r8tXKr1xU0MCvTgn12..."
}4. 加密流程
- 准备包含公共参数和业务参数的完整 JSON 数据
- 使用 PKCS7 填充方式将数据填充至 16 字节的倍数
- 使用 32 位密钥和初始化向量(IV,密钥的前 16 位)通过 AES-CBC 模式加密数据
- 将加密后的数据进行 Base64 编码,得到参数
x的值 - 构造最终请求体
{"x": "加密后的Base64字符串"} - 在 HTTP 头部添加 merchant-id
5. 解密流程
- 从请求体中提取参数
x的值 - 对密文进行 Base64 解码
- 使用相同的 32 位密钥和初始化向量(IV, 密钥的前 16 位)通过 AES-CBC 模式解密数据
- 去除 PKCS7 填充,得到原始 JSON 数据
- 解析 JSON 数据,提取公共参数和业务参数
6. 代码示例
6.1 Java 实现
java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class APIRequestExample {
private static final String SECRET_KEY = "32位密钥,需要和服务端一致";
private static final String IV = "16位初始向量";
private static final String MERCHANT_ID = "M202405120001";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static void sendRequest(String apiUrl, Map<String, Object> businessParams) throws Exception {
// 添加公共参数
businessParams.put("timestamp", System.currentTimeMillis());
businessParams.put("request_id", generateRequestId());
// 将完整参数转为JSON
String jsonData = OBJECT_MAPPER.writeValueAsString(businessParams);
// 加密
String encryptedData = encrypt(jsonData, SECRET_KEY, IV);
// 构造最终请求体
Map<String, String> requestBody = new HashMap<>();
requestBody.put("x", encryptedData);
String finalRequestBody = OBJECT_MAPPER.writeValueAsString(requestBody);
// 发送HTTP请求
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(apiUrl);
// 设置请求头
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("merchant-id", MERCHANT_ID);
// 设置请求体
StringEntity entity = new StringEntity(finalRequestBody);
httpPost.setEntity(entity);
// 执行请求
httpClient.execute(httpPost);
}
}
private static String encrypt(String plainText, String key, String iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes("UTF-8"));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encrypted);
}
private static String generateRequestId() {
return "req_" + System.nanoTime();
}
public static void main(String[] args) throws Exception {
Map<String, Object> params = new HashMap<>();
params.put("game_id", "game001");
params.put("user_id", "user123");
params.put("amount", 100);
sendRequest("https://api.example.com/game/action", params);
}
}6.2 PHP 实现
php
function sendRequest($apiUrl, $businessParams) {
// 添加公共参数
$businessParams['timestamp'] = round(microtime(true) * 1000);
$businessParams['request_id'] = 'req_' . uniqid();
// 转为JSON
$jsonData = json_encode($businessParams);
// 加密
$key = "32位密钥,需要和服务端一致";
$iv = "16位初始向量";
$encryptedData = encrypt($jsonData, $key, $iv);
// 构造最终请求体
$requestBody = ['x' => $encryptedData];
$finalRequestBody = json_encode($requestBody);
// 设置HTTP头
$headers = [
'Content-Type: application/json',
'merchant-id: M202405120001'
];
// 发送请求
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $finalRequestBody);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
function encrypt($plaintext, $key, $iv) {
$method = "AES-256-CBC";
$encrypted = openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encrypted);
}
// 使用示例
$params = [
'game_id' => 'game001',
'user_id' => 'user123',
'amount' => 100
];
$response = sendRequest("https://api.example.com/game/action", $params);
echo $response;6.3 Node.js 实现
javascript
const crypto = require('crypto')
const axios = require('axios')
async function sendRequest(apiUrl, businessParams) {
// 添加公共参数
businessParams.timestamp = Date.now()
businessParams.request_id = 'req_' + Date.now() + Math.random().toString(36).substring(2, 10)
// 转为JSON
const jsonData = JSON.stringify(businessParams)
// 加密
const key = '32位密钥,需要和服务端一致'
const iv = '16位初始向量'
const encryptedData = encrypt(jsonData, key, iv)
// 构造最终请求体
const requestBody = { x: encryptedData }
// 设置HTTP头
const headers = {
'Content-Type': 'application/json',
'merchant-id': 'M202405120001',
}
// 发送请求
try {
const response = await axios.post(apiUrl, requestBody, { headers })
return response.data
} catch (error) {
console.error('Request failed:', error)
throw error
}
}
function encrypt(plaintext, key, iv) {
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), Buffer.from(iv))
let encrypted = cipher.update(plaintext, 'utf8', 'base64')
encrypted += cipher.final('base64')
return encrypted
}
// 使用示例
async function test() {
const params = {
game_id: 'game001',
user_id: 'user123',
amount: 100,
}
try {
const response = await sendRequest('https://api.example.com/game/action', params)
console.log('Response:', response)
} catch (error) {
console.error('Error:', error)
}
}
test()7. 注意事项
- HTTP 请求头必须包含
merchant-id商户编号 - 公共参数
timestamp和request_id是必须的 timestamp必须是 13 位毫秒级时间戳request_id必须保证唯一性- 密钥长度必须为 32 字节(256 位)
- IV 长度必须为 16 字节(128 位)
- 确保所有业务参数与公共参数一起加密
- 最终请求体只包含单个参数
x - GET 请求的接口,是路由参数的话,最终加密的数据需要 url 编码
8. 错误处理
常见错误及处理方法:
- 密钥不正确 - 确认商户密钥是否正确
- IV 不正确 - 确认 IV 是否正确
- merchant-id 不存在 - 确认 HTTP 头部是否正确设置
- 时间戳过期 - 确保系统时间准确
- 请求 ID 重复 - 确保每次请求生成唯一 ID
返回示例
所有哈希游戏提供的接口,均使用如下标准返回。
json
{
"code": 0,
"msg": "success",
"data": {}
}- code: 返回码,0 为成功,不等于 0 表示发生错误。
- msg: 返回信息,当 code != 0 时,msg 包含了错误信息。
- data: 当成功返回存在数据内容时,会以对象或者数组的方式存在于 data 属性中;当无数据返回需要时,data 属性不存在。

