Skip to content

HMAC签名鉴权

本文档定义基于HMAC签名的轻量级鉴权方案,适用于资源受限的IoT设备。

设计目标

  • 轻量级:设备端仅需实现HMAC-SHA256
  • 防泄露:secret永不传输,即使请求被截获也无法得知
  • 防重放:通过时间戳限制请求有效期

前置条件

设备在配网阶段已获得secret(参考GATT配网协议),该secret作为HMAC密钥使用。

签名算法

签名计算

signature = HMAC-SHA256(secret, payload)

其中payload为待签名的请求内容(不含signature字段本身)。

请求格式

字段类型必填说明
action字符串操作类型
timestamp整型请求时间戳(秒)
signature字符串HMAC-SHA256签名(十六进制)
......-其他业务字段

签名步骤

  1. 构造请求JSON(不含signature字段)
  2. 对JSON字符串计算HMAC-SHA256(secret, json)
  3. 将签名转为十六进制字符串,添加到请求中

验证流程

设备端收到请求后:

  1. 提取signature:从请求中取出并移除signature字段
  2. 计算签名:对剩余JSON计算HMAC-SHA256
  3. 比对签名:计算结果与收到的signature是否一致
  4. 检查时间戳: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

物联网设备通信协议文档