3. 设备唯一标识生成与注册表绑定

设备唯一标识生成与注册表绑定 学习卡片

为什么需要“芯片级”唯一标识

在设备认证体系中,设备ID是身份的第一锚点。如果ID可以被复制、篡改或批量伪造,后续的密钥、证书、令牌都失去意义。因此本案例选择从硬件根信任出发:优先读取MCU/SoC的芯片唯一ID(UID),或从安全元件(SE/ATECC608A等)读取出厂固化序列号,再结合产品型号与批次信息,生成不可篡改的设备ID。

核心原则:设备ID必须来源于硬件不可写区域,而非软件配置或随机数生成后写入普通Flash。软件可写的ID在物理接触或固件漏洞下可被克隆。

设备ID的组成与生成规则

以STM32系列为例,96位UID位于固定系统存储区,只读且全球唯一。我们将其格式化为可读、可索引的字符串:

设备ID = "IOT-" + 产品型号 + "-" + UID_HEX(前12字节)

示例(教学假设值):
  芯片UID(96bit) = 0x1FFF_3A4B_5C6D_7E8F_90A1_B2C3
  产品型号       = STM32L431
  设备ID         = IOT-STM32L431-1FFF3A4B5C6D7E8F90A1B2C3

读取UID的参考实现(STM32 HAL):

#include "stm32l4xx_hal.h"
#include <stdio.h>
#include <string.h>

#define UID_BASE  0x1FFF7590UL   /* STM32L431 UID 起始地址 */
#define UID_WORDS 3              /* 96 bit = 3 x 32 bit */

void build_device_id(char *out, size_t out_len, const char *model)
{
    uint32_t uid[UID_WORDS];
    for (int i = 0; i < UID_WORDS; i++) {
        uid[i] = *(volatile uint32_t *)(UID_BASE + i * 4);
    }
    snprintf(out, out_len, "IOT-%s-%08lX%08lX%08lX",
             model,
             (unsigned long)uid[0],
             (unsigned long)uid[1],
             (unsigned long)uid[2]);
}
若使用ATECC608A,则通过I²C读取其出厂序列号(9字节),同样拼接为设备ID。关键是:读取路径必须经过硬件总线,且序列号不可被改写。

注册表绑定:设备ID ↔ 认证凭据

生成设备ID后,需要在设备注册表中建立“设备ID → 认证凭据”的绑定关系。注册表可以是本地JSON文件、SQLite数据库或云端设备管理服务。本案例以SQLite为例,表结构如下:

字段类型说明
device_idTEXT PRIMARY KEY硬件UID生成的唯一ID
credential_typeTEXTpsk / x509 / hmac
credential_hashTEXT凭据的SHA-256哈希(不存明文)
statusTEXTactive / revoked / pending
registered_atINTEGER注册时间戳

绑定操作示例(Python + sqlite3):

import sqlite3, hashlib, time

def register_device(db_path, device_id, credential):
    cred_hash = hashlib.sha256(credential.encode()).hexdigest()
    conn = sqlite3.connect(db_path)
    conn.execute("""
        INSERT INTO device_registry
        (device_id, credential_type, credential_hash, status, registered_at)
        VALUES (?, 'psk', ?, 'active', ?)
    """, (device_id, cred_hash, int(time.time())))
    conn.commit()
    conn.close()

验证未注册设备无法通过认证

认证服务端在收到设备连接请求时,先提取设备ID,再查询注册表。若device_id不存在或status不为active,直接拒绝。验证流程如下:

  1. 设备上报 device_id = IOT-STM32L431-1FFF3A4B5C6D7E8F90A1B2C3
  2. 服务端查询注册表,命中且status=active → 继续凭据校验
  3. 若查询无结果 → 返回 AUTH_DENIED,断开连接
  4. 若status=revoked → 同样拒绝,并记录审计日志
注意:设备ID本身不是秘密,不应作为认证凭据单独使用。它只是索引键,真正的认证仍依赖PSK、证书或HMAC令牌。本案例的重点是“绑定关系”和“未注册即拒绝”。
设备端 芯片UID (96bit) 0x1FFF3A4B...B2C3 设备ID生成 IOT-STM32L431-... 认证凭据 PSK / 证书 / HMAC密钥 设备注册表 device_id | credential_hash | status IOT-STM32L431-1FFF... | a3f8... | active IOT-STM32L431-FFFF... | — | 不存在 绑定关系 device_id → credential_hash 凭据不存明文,仅存SHA-256 认证服务 查询 device_id SELECT ... WHERE ... 存在且 active? 是 继续凭据校验 PSK / 证书 / HMAC 否 AUTH_DENIED 断开连接 + 审计日志 设备ID来源于硬件UID,注册表绑定凭据哈希,未注册设备在查询阶段即被拒绝

验证方法与通过条件

完成绑定后,用以下方式验证“未注册设备无法通过认证”:

  • 正向验证:使用已注册设备ID和正确PSK连接,应成功建立MQTT会话。
  • 反向验证:使用一个未写入注册表的设备ID(如手动构造 IOT-STM32L431-FFFFFFFFFFFFFFFFFFFFFFFF),连接应被拒绝,服务端返回 AUTH_DENIED。
  • 篡改验证:将已注册设备的UID读取地址改为普通Flash地址,重启后设备ID变化,注册表查询失败,认证被拒。

通过条件:正向连接成功,反向连接在注册表查询阶段即被拒绝,且服务端日志中记录拒绝原因。