外观
签名算法
所有客户端 API 请求都需要携带签名参数,用于验证请求的合法性和完整性。NullVerify 支持 MD5 和 HMAC-SHA256 两种签名算法。
请求签名
MD5 签名(默认)
sign = md5(k1=v1&k2=v2&...&kn=vn + app_secret)HMAC-SHA256 签名
sign = hmac_sha256(key=app_secret, data=k1=v1&k2=v2&...&kn=vn)说明
HMAC-SHA256 模式下,app_secret 作为 HMAC 密钥,不拼接到原始字符串中。
参数说明
| 参数 | 说明 |
|---|---|
k1=v1&...&kn=vn | 所有请求参数(不含 sign)按 key 字典序升序排列,以 & 连接 |
app_secret | 应用密钥 |
签名步骤
- 收集所有请求参数(不含
sign) - 按 key 的字典序升序排列
- 拼接为
key1=value1&key2=value2&...格式 - MD5 模式:末尾追加
app_secret,计算 MD5 哈希值(小写十六进制) - HMAC-SHA256 模式:以
app_secret为密钥,对参数字符串计算 HMAC-SHA256
签名示例
以卡密登录接口为例:
python
import hashlib
app_secret = "uiS9M0G8JolpUvlf5NxZ7pwMVinKs73x"
# 参数按 key 字典序排列
params = "app_key=blsvh14llhcr96vtboqg&card=abc3b65KDZ9Qb7UC685D2MVFR0TPc53BCU1IPD5ad20&device_id=123&nonce=359c22e4-d522-4771-ba8e-4b99cf61b372×tamp=1574654197"
# 拼接并计算
raw = params + app_secret
sign = hashlib.md5(raw.encode()).hexdigest()
print(sign) # 输出签名值注意
- 签名原文应使用参数的原始值,不要对 URL 编码后的字符串参与签名
sign参数本身不参与签名计算- 参数必须严格按照 key 的字典序排列
公共请求参数
所有客户端 API 请求都需要携带以下公共参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
app_key | string | 是 | 应用标识,在管理后台获取 |
timestamp | int | 是 | 当前时间戳(秒),签名在 60 秒内有效 |
nonce | string | 是 | 随机字符串(建议 UUID),90 秒内不可重复 |
sign | string | 是 | 请求签名 |
timestamp 说明
服务器会校验 timestamp 与当前服务器时间的差值,超过 60 秒的请求将被拒绝。请确保客户端时钟与服务器时间偏差不超过 1 分钟。
nonce 说明
nonce 用于防止重放攻击。对于同一应用,服务器会记录每次请求的 nonce,如果在 90 秒内出现重复的 nonce,请求将被拒绝。
各接口签名示例
卡密登录 (POST /card/login)
参数:app_key, card, device_id, nonce, timestamp
签名原文 = "app_key=blsvh14l&card=ABC123&device_id=fingerprint_001&nonce=uuid-xxx×tamp=1712505600" + app_secret
sign = md5(签名原文)卡密心跳 (POST /card/heartbeat)
参数:app_key, card, nonce, timestamp, token
签名原文 = "app_key=blsvh14l&card=ABC123&nonce=uuid-yyy×tamp=1712505610&token=sess_token" + app_secret
sign = md5(签名原文)卡密退出 (POST /card/logout)
参数:app_key, card, nonce, timestamp, token
签名原文 = "app_key=blsvh14l&card=ABC123&nonce=uuid-zzz×tamp=1712505620&token=sess_token" + app_secret
sign = md5(签名原文)客户端只需一个通用签名函数:把所有参数排序拼接 + secret,无需关心接口路径。
响应签名(协议能力)
系统已实现响应签名算法,但当前客户端默认响应链路尚未启用 nonce 和 sign 字段输出。以下内容描述的是协议能力,待后续启用后生效。
响应签名公式
sign = md5(code + message + k1=v1&k2=v2&...&kn=vn + nonce + app_secret)| 参数 | 说明 |
|---|---|
code | 响应码,如 0 |
message | 响应消息,如 ok |
k1=v1&...&kn=vn | result 中所有字段按 key 字典序排列 |
nonce | 服务端返回的随机字符串,保证唯一且趋势递增 |
app_secret | 应用密钥 |
验签示例
python
import hashlib
app_secret = "uiS9M0G8JolpUvlf5NxZ7pwMVinKs73x"
prev_nonce = None # 记录上次服务端返回的 nonce
def check_response_sign(resp):
global prev_nonce
# 校验 nonce 递增
if prev_nonce is not None and resp["nonce"] <= prev_nonce:
return False # nonce 未递增,可能是重放
prev_nonce = resp["nonce"]
# 构建 result 参数字符串
result = resp.get("result", {})
params = sorted(f"{k}={v}" for k, v in result.items())
param_str = "&".join(params)
# 拼接并计算签名
raw = f"{resp['code']}{resp['message']}{param_str}{resp['nonce']}{app_secret}"
expected_sign = hashlib.md5(raw.encode()).hexdigest()
return expected_sign == resp["sign"]响应示例
json
{
"code": 0,
"message": "ok",
"result": {
"expires": "2024-12-31 23:59:59",
"expires_ts": 1735689599,
"server_time": 1703001600
},
"nonce": "bojc2kiuof2jci9b90jg",
"sign": "4954c9805d4040a95336150e6e5f14e2"
}常见签名问题排查
| 问题 | 排查方向 |
|---|---|
| 签名不一致 (10010) | 检查参数排序是否正确、参数值是否进行了 URL encode、app_secret 是否正确 |
| 签名已过期 (10011) | 检查客户端时钟是否准确,timestamp 与服务器时差不超过 60 秒 |
| 时间戳异常 (10013) | timestamp 大于服务器当前时间,检查客户端时钟 |
| nonce 重复 (10014) | 确保每次请求使用不同的 nonce,建议使用 UUID |