跳转至

NFC 绑定

sim-hw 每次启动时都会生成 NFC 载荷,模拟嵌入在物理 Inklet 设备中的 NFC 标签。该载荷用于实现触碰配对的设备绑定。

载荷格式

inklet:1:{hw_id}:{signature}
组件 描述
inklet 协议标识符
1 版本号
{hw_id} 设备硬件 UUID(例如 a1b2c3d4-5678-9012-abcd-ef0123456789
{signature} HMAC-SHA256(hw_id, FACTORY_SECRET) 的前 16 个十六进制字符

示例:

inklet:1:a1b2c3d4-5678-9012-abcd-ef0123456789:3f7a8b2c1d9e0f4a

签名生成

签名使用 HMAC-SHA256 计算,以出厂密钥作为密钥,硬件 UUID 作为消息。仅使用完整 HMAC 摘要的前 16 个十六进制字符(8 字节)。

import hashlib
import hmac


def generate_nfc_signature(hw_id: str, factory_secret: str) -> str:
    """Generate the NFC signature for a device hardware ID.

    Args:
        hw_id: Device hardware UUID string.
        factory_secret: Hex-encoded factory secret (32 bytes = 64 hex chars).

    Returns:
        First 16 hex characters of HMAC-SHA256(hw_id, factory_secret).
    """
    secret_bytes = bytes.fromhex(factory_secret)
    mac = hmac.new(secret_bytes, hw_id.encode("utf-8"), hashlib.sha256)
    return mac.hexdigest()[:16]

使用方式:

hw_id = "a1b2c3d4-5678-9012-abcd-ef0123456789"
secret = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

signature = generate_nfc_signature(hw_id, secret)
payload = f"inklet:1:{hw_id}:{signature}"
print(payload)
# inklet:1:a1b2c3d4-5678-9012-abcd-ef0123456789:3f7a8b2c1d9e0f4a

为什么截断到 16 个字符?

NFC 标签的存储容量有限。将 HMAC 截断到 8 字节(16 个十六进制字符)在满足 NFC 载荷大小限制的同时提供了合理的安全裕度。64 位的签名空间使得暴力攻击在设备绑定场景中不切实际。

载荷文件

sim-hw 每次启动时将 NFC 载荷写入 {data-dir}/nfc-payload。你可以读取此文件来模拟扫描设备的 NFC 标签。

cat devices/kitchen/nfc-payload
# inklet:1:a1b2c3d4-5678-9012-abcd-ef0123456789:3f7a8b2c1d9e0f4a

绑定流程

完整的 NFC 绑定流程如下:

物理设备                               sim-hw 等效操作
────────────────                       ─────────────────
设备上的 NFC 标签             →           数据目录中的 nfc-payload 文件
用户将手机触碰设备            →           用户从文件中复制载荷
App 读取 NFC 载荷             →           用户在 sim-dashboard 中粘贴载荷
App 提取 hwId + sig           →           sim-dashboard 解析载荷
App 调用 bind/nfc API         →           sim-dashboard 调用 bind/nfc API
后端验证 HMAC                 →           后端验证 HMAC(相同)
设备绑定到用户                →           设备绑定到用户(相同)

详细步骤:

  1. App 读取 NFC --- 在现实中,移动应用读取 NFC 标签。在模拟器中,你复制 nfc-payload 文件的内容。

  2. App 调用 API --- 应用(或 sim-dashboard)发送 POST /api/devices/bind/nfc 请求,包含提取的 hwIdsignature

    curl -X POST https://auth.iminklet.com/api/devices/bind/nfc \
      -H "Authorization: Bearer {accessToken}" \
      -H "Content-Type: application/json" \
      -d '{"hwId": "a1b2c3d4-5678-9012-abcd-ef0123456789", "signature": "3f7a8b2c1d9e0f4a"}'
    
  3. 后端验证 --- 后端重新计算 HMAC-SHA256(hwId, FACTORY_SECRET),并将前 16 个十六进制字符与提供的签名进行比较。如果匹配,则将设备绑定到当前认证用户。

  4. 设备接收通知 --- 后端向设备的 MQTT 主题发布 bound 命令。sim-hw 收到后显示绑定确认信息。

安全注意事项

出厂密钥

FACTORY_SECRET 在 sim-hw 和后端之间必须一致。在生产环境中,该密钥在出厂时烧录到设备中,并在后端的环境变量中配置。切勿在客户端代码或日志中暴露出厂密钥。

  • HMAC 签名可防止未授权绑定 --- 不知道出厂密钥就无法绑定设备。
  • 每个设备都有唯一的硬件 UUID,因此签名是设备专属的。
  • 签名是确定性的:相同的 hw_idFACTORY_SECRET 始终生成相同的签名。这是有意为之的 --- NFC 标签的内容永远不会改变。

测试 NFC 绑定

使用 sim-hw 测试 NFC 绑定流程:

  1. 启动模拟设备:

    python -m eink_hw --data-dir devices/kitchen
    
  2. 读取 NFC 载荷:

    cat devices/kitchen/nfc-payload
    
  3. 在 sim-dashboard 中,点击"Bind Device"并粘贴 NFC 载荷字符串。

  4. 或者,直接调用 API:

    curl -X POST http://localhost:4000/api/devices/bind/nfc \
      -H "Authorization: Bearer {your-token}" \
      -H "Content-Type: application/json" \
      -d '{"hwId": "...", "signature": "..."}'
    
  5. 设备应显示"Device bound successfully"并出现在你的仪表盘中。