开放数据校验与解密
前言
小游戏可以通过前端接口获取 SUD OpenPaaS 提供的开放数据。考虑到 Game SVR 也需要使用这些数据,SUD OP 会对开放数据做签名和加密处理,Game SVR 拿到数据后可以校验签名与解密,以保证数据不被篡改。
签名校验、数据解密以及登录态校验都涉及用户的会话密钥 session_key。Game SVR 应事先通过登录流程调用 auth.code2session 获取 session_key 并保存在服务端。为了数据不被篡改,严禁把 session_key 下发到小游戏客户端等 Game SVR 之外的环境。
本文涉及三套以
session_key为密钥的机制:数据签名校验、加密数据解密、用户态签名。三者对session_key的处理方式互不兼容,实现时请勿套用另一节的写法,差异对照见第五节「三套机制的区分」。
一、数据签名校验
为了确保开放接口返回的用户数据未被篡改,SUD OP 会对明文数据进行签名。接口在返回 rawData 的同时会返回 signature:
signature = to_hex( sha1( rawData + session_key ) )Game SVR 使用该用户对应的 session_key、以相同算法计算出 signature2,比对 signature 与 signature2 即可校验数据完整性。
注意此处是原文与密钥直接拼接后做 SHA1,并非 HMAC,与第三节「用户态签名」是不同的算法。完整的分步说明与返回参数见用户敏感数据获取「数据处理约束与算法规范」一节。
二、加密数据解密算法
接口如果涉及敏感数据(如手机号),明文内容将不包含这些字段,Game SVR 需要对接口返回的加密数据 encryptedData 进行对称解密。解密算法如下:
- 对称解密使用的算法为 AES-128-CBC,数据采用 PKCS#7 填充。
- 对称解密的目标密文为
Base64_Decode(encryptedData)。 - 对称解密密钥
aeskey = Base64_Decode(session_key),aeskey是 16 字节。 - 对称解密算法初始向量为
Base64_Decode(iv),其中iv由数据接口返回。
完整的加解密流程、返回参数与安全规范见用户敏感数据获取。
三、用户态签名
部分服务端接口需要使用 session_key 校验用户身份。为了保持 session_key 的私密性,接口不明文传输 session_key,而是通过校验用户态签名 signature 完成身份确认。
目前使用用户态签名的场景为支付下单,参见支付签名规范。
3.1 算法规范
signature = to_hex( hmac_sha256( session_key, rawData ) )其中 rawData 为 Game SVR 在请求服务端接口时携带的参数原串。
| 项 | 取值 |
|---|---|
| 算法 | HMAC-SHA256 |
| 密钥 | session_key(直接使用字符串本身,见 3.2) |
| 待签名消息 | rawData 的 UTF-8 字节 |
| 输出编码 | 小写十六进制字符串 |
3.2 密钥的处理方式
本机制直接使用
session_key的 Base64 字符串本身作为 HMAC 密钥,不要做 Base64 解码。例如
auth.code2session返回的session_key为Wh/vJSqjuUvFiX7Zaecgrw==时,HMAC 密钥就是这 24 个字符的 ASCII 字节,而不是解码后的 16 字节。
3.3 待签名串的字节一致性
签名必须针对最终传输的那个字符串的字节序列计算。
严禁「先签名,再把 rawData 反序列化后重新序列化并传输」——任何重新序列化都可能改变空格、换行、键顺序或转义形式,导致 SUD OP SVR 验签失败。正确做法是:序列化一次得到字符串,用同一个字符串变量计算签名并传输。
同理,Game SVR 在校验 SUD OP 返回的签名时,也必须使用收到的原始 rawData 字符串,不得解析后重新序列化。
3.4 计算示例
以支付下单的 signData 作为 rawData 为例。
| 项 | 取值 |
|---|---|
session_key | Wh/vJSqjuUvFiX7Zaecgrw== |
rawData 字节长度 | 148 |
rawData:
{"mode":"goods","env":0,"productId":"gem_100","productName":"100钻石","quantity":1,"unitPrice":600,"currency":"CNY","outTradeNo":"BIZ20250420001"}使用 HMAC-SHA256 得到的结果为:
691b1c5c727afaf70454d6421798cd911963f1df7652360134fde92856dfc8183.5 示例代码
Golang:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
// sessionKey 为 code2session 返回的 Base64 字符串,直接使用,不要解码
func calcSignature(sessionKey, rawData string) string {
mac := hmac.New(sha256.New, []byte(sessionKey))
mac.Write([]byte(rawData))
return hex.EncodeToString(mac.Sum(nil))
}Node.js:
const crypto = require('crypto')
// sessionKey 为 code2session 返回的 Base64 字符串,直接使用,不要 Buffer.from(key, 'base64')
function calcSignature(sessionKey, rawData) {
return crypto.createHmac('sha256', sessionKey).update(rawData, 'utf8').digest('hex')
}四、会话密钥 session_key 有效性
如果遇到因 session_key 不正确而导致验签失败或解密失败,请关注以下几点:
- SUD OP 不会将
session_key的有效期告知开发者。 请勿在代码中写入固定的过期时长,也不要基于猜测的有效期做缓存淘汰。 - 小游戏重新登录时,用户的
session_key可能被更新而致使旧session_key失效。Game SVR 应在明确需要重新登录时才触发登录流程,并及时通过auth.code2session更新服务端保存的session_key。 session_key失效的表现是验签一直不通过或解密失败,而非明确的密钥过期报错。排查签名问题时,请把「会话是否已失效」与「待签名串是否构造错误」作为两个独立方向分别确认。session_key失效时,通过重新执行登录流程即可获取有效的session_key。- Game SVR 实现自定义登录态时,可以以
session_key的生命周期作为自身登录态的参考,也可以实现自定义的时效性策略,详见登录态管理。
五、三套机制的区分
| 机制 | 出现场景 | 密钥处理方式 | 算法 |
|---|---|---|---|
| 数据签名校验 | 开放数据返回 rawData + signature | 直接拼接在原文之后 | SHA1(非 HMAC) |
| 加密数据解密 | 敏感数据 encryptedData + iv | Base64 解码为 16 字节 | AES-128-CBC / PKCS#7 |
| 用户态签名 | 需校验登录态的服务端接口(当前为支付下单) | 直接使用字符串本身 | HMAC-SHA256 |
三者都以 session_key 为密钥,但处理方式互不兼容。此外还有一个容易混淆之处:数据签名校验与用户态签名的字段都叫 signature,但前者是 SHA1 拼接哈希、后者是 HMAC-SHA256,请以各自章节为准,不要跨机制复用代码。
以上三套机制处理的都是业务数据层面的签名与加解密,与服务端 API 签名加密的 HTTP 报文层加密签名是两套独立机制,请勿混用。
