Appearance
HMAC签名鉴权
本文档定义基于HMAC签名的轻量级鉴权方案,适用于资源受限的IoT设备。
设计目标
- 轻量级:设备端仅需实现HMAC-SHA256
- 防泄露:secret永不传输,即使请求被截获也无法得知
- 防重放:通过时间戳限制请求有效期
前置条件
设备在配网阶段已获得secret(参考GATT配网协议),该secret作为HMAC密钥使用。
签名算法
签名计算
signature = HMAC-SHA256(secret, payload)其中payload为待签名的请求内容(不含signature字段本身)。
请求格式
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | 字符串 | 是 | 操作类型 |
| timestamp | 整型 | 是 | 请求时间戳(秒) |
| signature | 字符串 | 是 | HMAC-SHA256签名(十六进制) |
| ... | ... | - | 其他业务字段 |
签名步骤
- 构造请求JSON(不含signature字段)
- 对JSON字符串计算
HMAC-SHA256(secret, json) - 将签名转为十六进制字符串,添加到请求中
验证流程
设备端收到请求后:
- 提取signature:从请求中取出并移除signature字段
- 计算签名:对剩余JSON计算HMAC-SHA256
- 比对签名:计算结果与收到的signature是否一致
- 检查时间戳:timestamp是否在有效窗口内(建议±300秒)
示例
请求示例
原始请求(签名前):
json
{
"action": "unlock",
"timestamp": 1710000000,
"door_id": 1
}计算签名:
secret = "550e8400-e29b-41d4-a716-446655440000"
payload = '{"action":"unlock","timestamp":1710000000,"door_id":1}'
signature = HMAC-SHA256(secret, payload)
= "a1b2c3d4e5f6..."最终请求:
json
{
"action": "unlock",
"timestamp": 1710000000,
"door_id": 1,
"signature": "a1b2c3d4e5f6..."
}响应示例
成功:
json
{
"status": "success"
}签名无效:
json
{
"status": "error",
"error": "invalid_signature"
}请求过期:
json
{
"status": "error",
"error": "request_expired"
}实现参考
JavaScript (APP端)
javascript
const crypto = require('crypto')
function signRequest(request, secret) {
const payload = JSON.stringify(request)
const signature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return { ...request, signature }
}
// 使用示例
const request = {
action: 'unlock',
timestamp: Math.floor(Date.now() / 1000),
door_id: 1
}
const signed = signRequest(request, '550e8400-e29b-41d4-a716-446655440000')C语言 (设备端)
c
#include <mbedtls/md.h>
#include <string.h>
#include <stdio.h>
// 计算HMAC-SHA256
void hmac_sha256(const char* secret, const char* payload,
uint8_t* output) {
mbedtls_md_context_t ctx;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
mbedtls_md_hmac_starts(&ctx, (uint8_t*)secret, strlen(secret));
mbedtls_md_hmac_update(&ctx, (uint8_t*)payload, strlen(payload));
mbedtls_md_hmac_finish(&ctx, output);
mbedtls_md_free(&ctx);
}
// 验证签名
bool verify_signature(const char* secret, const char* payload,
const char* signature_hex) {
uint8_t computed[32];
hmac_sha256(secret, payload, computed);
// 转为十六进制比对
char computed_hex[65];
for (int i = 0; i < 32; i++) {
sprintf(&computed_hex[i*2], "%02x", computed[i]);
}
return strcmp(computed_hex, signature_hex) == 0;
}安全考虑
时间同步
设备端需要有准确的时间,可通过以下方式获取:
- 配网时APP传入timestamp
- 通过NTP同步
- MQTT连接时服务端下发时间
时间窗口
建议时间窗口为±300秒(5分钟),在设备端可配置:
- 窗口过小:对时间同步要求高,可能误拒合法请求
- 窗口过大:防重放效果降低
与TLS配合
本方案可与TLS传输配合使用:
- TLS:保护传输过程,防止中间人攻击
- HMAC签名:验证请求来源,防止非授权访问
局限性
| 场景 | 是否适用 |
|---|---|
| MQTT over TLS | ✅ 推荐 |
| MQTT无TLS | ⚠️ 可用但传输不加密 |
| 蓝牙GATT | ⚠️ 需考虑JSON体积 |
| 高安全要求 | ❌ 建议使用端对端安全V2 |
