设备动态注册
约 2587 字大约 9 分钟
2025-03-03
简介
用户可以通过设备向云平台发送注册请求实现在云平台上注册设备。
应用场景
- 一机一密场景:需要在生产环节为每一个设备烧录由平台生成的唯一设备信息。
- 一型一密场景:同一产品类型的设备只要预置产品级密钥,可省去工厂的设备信息烧录。设备出厂后,在第一次开机且成功联网的情况下,可发起动态注册,通过产品密钥鉴权,从平台获取设备信息,然后存在本地,达成和工厂烧录一样的效果。
使用步骤
在控制台使能产品的动态注册功能
使能动态注册后才有产品密钥。

产品密钥:

选择是否使能自动设备创建
这个选项决定动态注册时的设备名,是设备自定义的(需要保证统一产品下设备名唯一),还是使用平台预定义的设备名。
调用设备动态注册接口进行设备注册
MQTT自动注册
参数说明
- {productID}:产品ID。
- {deviceName}:设备ID。
- 通用请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| msgToken | String | 是 | 用于上下行消息配对标识 |
| sys.noAsk | bool | 否 | 云端是否回复设备 |
| sys.retMsg | bool | 否 | 是否返回错误信息,字段为msg |
| 示例: |
{
"method": "reportInfo",
"msgToken": "1234567",
"sys": {
"noAsk": false,
"retMsg": true
}
}登录
MQTT的clientID使用 register&{productID}&{deviceName},userName和password可以随便填。
连接示例(以 mosquitto 命令行工具为例):
# 先在另一个终端订阅下行响应 Topic
mosquitto_sub -h 127.0.0.1 -p 1883 \
-i "register&pxxxxxxxxxxxxx&my-device-001" \
-u "any" -P "any" \
-t '$ext/down/register/pxxxxxxxxxxxxx/my-device-001'
# 发布上行注册请求
mosquitto_pub -h 127.0.0.1 -p 1883 \
-i "register&pxxxxxxxxxxxxx&my-device-001" \
-u "any" -P "any" \
-t '$ext/up/register/pxxxxxxxxxxxxx/my-device-001' \
-m '{
"msgToken": "1234567",
"payload": {
"nonce": 2125656451,
"timestamp": 1687525491,
"signature": "<Base64(HMAC-SHA1(productSecret, \"deviceName=my-device-001&nonce=2125656451&productID=pxxxxxxxxxxxxx×tamp=1687525491\"))>",
"retEnc": "aes128cbc"
}
}'说明: clientID 格式固定为
register&{productID}&{deviceName},userName 和 password 可填任意值。
Topic
- 设备属性上行请求 Topic:
$ext/up/register/{productID}/{deviceName} - 设备属性下行响应 Topic:
$ext/down/register/{productID}/{deviceName}
请求参数
示例:
{
"payload": {
"nonce": 123,
"timestamp": 123123123,
"signature": "wefwefwegefsfsef",
"retEnc": "aes128cbc", // 返回使用的加密方式: aes128cbc(默认) aes128ecb
"tenantCode": "your-tenant-code" // 可选:指定设备归属租户
}
}| 参数名称 | 类型 | 必须 | 说明 |
|---|---|---|---|
| nonce | int64 | 是 | 随机数 |
| timestamp | int64 | 是 | 秒级时间戳 |
| signature | string | 是 | 签名信息 |
| retEnc | string | 否 | 返回使用的加密方式: aes128cbc(默认) aes128ecb |
| tenantCode | string | 否 | 租户号(可选):若填写,要求产品的租户号为 common 或与该值一致,否则返回错误;不填则使用产品的默认租户 |
tenantCode 使用规则
- 不填(默认):设备归属产品所在租户
- 产品租户为
common(全局共享产品):可填入任意目标租户号,设备将注册到该租户- 产品租户与
tenantCode一致:允许(幂等,等同不填)- 产品租户既非
common又与tenantCode不一致:返回错误"设备注册租户号与产品租户号不一致"
签名生成步骤
- 对参数(deviceName、nonce、productID、timestamp)按字典序升序排序。
- 将以上参数,按
参数名称=参数值&参数名称=参数值拼接成字符串,如:”deviceName=xxx&nonce=2125656451&productID=xxx×tamp=1687525491”。 - 以 ProductSecret 字符串的 ASCII 字节作为 HMAC 密钥(注意:不需要对 ProductSecret 进行 Base64 解码,控制台显示的是什么字符串就直接用什么字符串),使用 HMAC-SHA1 算法对上一步的字符串进行计算,得到原始字节。
- 将原始字节转为十六进制(HEX)字符串。
- 对十六进制字符串的 ASCII 字节进行 Base64 编码,即可获得最终签名,填入
signature字段。
重要说明:签名编码流程为
Base64( HEX( HMAC-SHA1(data, key) ) ),而非直接Base64( HMAC-SHA1(data, key) ),中间有一步十六进制编码,请勿遗漏。
Python 示例:
import hmac
import hashlib
import base64
def calc_signature(product_secret: str, data: str) -> str:
“””
计算动态注册签名
参数:
product_secret: 控制台获取的产品密钥(Base64格式,直接使用,不要解码!)
data: 拼接好的待签名字符串
返回:
signature 字段值
“””
# 密钥直接使用 ProductSecret 字符串的 ASCII 字节,不要 base64.b64decode!
key = product_secret.encode('ascii')
# Step 1: HMAC-SHA1,得到原始字节
raw_bytes = hmac.new(key, data.encode('utf-8'), hashlib.sha1).digest()
# Step 2: 十六进制编码
hex_str = raw_bytes.hex() # 等价于 binascii.hexlify(raw_bytes).decode()
# Step 3: 对十六进制字符串进行 Base64 编码
signature = base64.b64encode(hex_str.encode('ascii')).decode('ascii')
return signature
if __name__ == “__main__”:
product_secret = “3q4zRdjdfe5U2G3HX8VTbLCZuOE=” # 控制台复制的产品密钥
device_name = “my-device-001”
product_id = “pxxxxxxxxxxxxx”
nonce = 2125656451
timestamp = 1687525491
# 参数按字典序排序后拼接
data = f”deviceName={device_name}&nonce={nonce}&productID={product_id}×tamp={timestamp}”
sig = calc_signature(product_secret, data)
print(f”signature: {sig}”)平台返回参数
示例:
{
“timestamp”: 1709968346811,
“code”: 200,
“msg”: “ok”,
“data”: {
“len”: 10,
“payload”: “xxxxxxx”
}
}| 参数名称 | 类型 | 描述 |
|---|---|---|
| code | string | 错误码 |
| msg | string | 错误信息 |
| data | object | |
| - len | int64 | 长度 |
| - payload | string | 设备连接信息的 AES 加密后内容 |
说明: 加密过程将原始 JSON 格式的 payload 转为字符串后进行 AES 加密,再进行 Base64 编码。AES 加密算法根据请求的 retEnc 方式(aes128cbc 或 aes128ecb),密钥长度 128 位(16 字节),取 ProductSecret 字符串的前 16 个 ASCII 字符(不需要解码),偏移量(IV)为 16 个 ASCII 字符 ”0”(即 bytes.Repeat([]byte(“0”), 16))。
原始 payload 内容说明:
| key | value | 描述 |
|---|---|---|
| encryptionType | 1 | 加密类型,1表示证书认证,2表示签名认证。注意:当前版本该字段固定返回 1,请以 psk 是否非空来判断实际认证类型,见下方说明。 |
| psk | 1239465801 | 设备密钥,当产品认证类型为签名认证时有此参数。值为 base64 编码的随机字节串,连接时直接作为字符串使用(不需要再 base64 解码),见下方说明。 |
| clientCert | - | 设备证书文件字符串格式,当产品认证类型为证书认证时有此参数 |
| clientKey | - | 设备私钥文件字符串格式,当产品认证类型为证书认证时有此参数 |
⚠️ 注意事项
1.
encryptionType字段当前固定为1当前版本无论产品认证方式如何,
encryptionType均返回1。设备端应 以psk是否非空 来判断实际认证类型:
psk非空,clientCert/clientKey为空 → 签名认证(PSK 模式),使用psk连接clientCert/clientKey非空,psk为空 → 证书认证,使用证书连接2.
psk的格式与使用方式
psk的值是平台生成的 base64 编码的随机字节串(20 字节随机数经 base64 编码),格式形如KiB+/WCfAGpwKoqqJ1Js3aAWtxk=。使用时直接将该字符串作为密钥(取其 ASCII 字节),无需再次 base64 解码。这与产品密钥(ProductSecret)的使用方式一致:
# ✅ 正确:直接使用 psk 字符串的 ASCII 字节 key = psk.encode('ascii') # ❌ 错误:不要对 psk 进行 base64 解码 key = base64.b64decode(psk)具体用途(如 MQTT 密码签名)请参考对应协议的接入文档。
payload 解密后原始内容示例(签名认证产品):
{
"encryptionType": 1,
"psk": "KiB+/WCfAGpwKoqqJ1Js3aAWtxk=",
"clientCert": "",
"clientKey": ""
}上例中
encryptionType为1但psk非空、cert 为空,说明是签名认证产品。判断逻辑以psk是否非空为准。
payload 解密后原始内容示例(证书认证产品):
{
"encryptionType": 1,
"psk": "",
"clientCert": "-----BEGIN CERTIFICATE-----\n...",
"clientKey": "-----BEGIN PRIVATE KEY-----\n..."
}HTTP自动注册
参数说明
设备动态注册时需携带 ProductID、ProductSecret 和 DeviceName 向平台发起 HTTP/HTTPS 请求,请求接口及参数如下:
请求的 URL 为:
- https://127.0.0.1:7777/api/v1/things/device/auth/register
- http://127.0.0.1:7777/api/v1/things/device/auth/register
请求方式: POST
请求头:
| Header | Value |
|---|---|
| Content-Type | application/json |
参数传递方式: 所有参数均以 JSON 格式放在请求体(Body)中,不使用 URL Query String 或 HTTP Header 传参。
请求参数
| 参数名称 | 类型 | 必须 | 说明 |
|---|---|---|---|
| productID | string | 是 | 产品ID |
| deviceName | string | 是 | 设备ID |
| nonce | int64 | 是 | 随机数 |
| timestamp | int64 | 是 | 秒级时间戳 |
| signature | string | 是 | 签名信息 |
| retEnc | string | 否 | 返回 payload 的加密方式:aes128cbc(默认,CBC 模式)或 aes128ecb(ECB 模式) |
| tenantCode | string | 否 | 租户号(可选):若填写,要求产品的租户号为 common 或与该值一致,否则返回错误;不填则使用产品的默认租户 |
请求示例:
curl -X POST "http://127.0.0.1:7777/api/v1/things/device/auth/register" \
-H "Content-Type: application/json" \
-d '{
"productID": "pxxxxxxxxxxxxx",
"deviceName": "my-device-001",
"nonce": 2125656451,
"timestamp": 1687525491,
"signature": "<Base64(HMAC-SHA1(productSecret, \"deviceName=my-device-001&nonce=2125656451&productID=pxxxxxxxxxxxxx×tamp=1687525491\"))>",
"retEnc": "aes128cbc",
"tenantCode": "your-tenant-code"
}'签名生成步骤
与 MQTT 注册完全相同,见上方"签名生成步骤"说明,算法为 Base64( HEX( HMAC-SHA1(data, key) ) ),密钥直接使用 ProductSecret 字符串的 ASCII 字节(不解码)。
平台返回参数
| 参数名称 | 类型 | 描述 |
|---|---|---|
| code | string | 错误码 |
| msg | string | 错误信息 |
| data | object | |
| - len | int64 | 长度 |
| - payload | string | 设备连接信息的 AES 加密后内容 |
说明: 加密过程将原始 JSON 格式的 payload 转为字符串后进行 AES 加密,再进行 Base64 编码。AES 加密算法为 CBC 模式,密钥长度 128 位(16 字节),取 ProductSecret 字符串的前 16 个 ASCII 字符(不解码),偏移量(IV)为 16 个 ASCII 字符 ”0”。
原始 payload 内容说明:
| key | value | 描述 |
|---|---|---|
| encryptionType | 1 | 加密类型,1表示证书认证,2表示签名认证。注意:当前版本该字段固定返回 1,请以 psk 是否非空来判断实际认证类型,见 MQTT 注册部分的说明。 |
| psk | 1239465801 | 设备密钥,当产品认证类型为签名认证时有此参数。值为 base64 编码的随机字节串,连接时直接作为字符串使用(不需要再 base64 解码),见 MQTT 注册部分的说明。 |
| clientCert | - | 设备证书文件字符串格式,当产品认证类型为证书认证时有此参数 |
| clientKey | - | 设备私钥文件字符串格式,当产品认证类型为证书认证时有此参数 |
响应示例:
{
“code”: “OK”,
“msg”: “success”,
“data”: {
“len”: 42,
“payload”: “<Base64(AES-CBC加密后的设备连接信息)>”
}
}payload 解密后原始内容示例(签名认证产品):
{
“encryptionType”: 1,
“psk”: “KiB+/WCfAGpwKoqqJ1Js3aAWtxk=”,
“clientCert”: “”,
“clientKey”: “”
}
psk为 base64 编码的随机字节串,直接用于连接,无需解码。
payload 解密后原始内容示例(证书认证产品):
{
“encryptionType”: 1,
“psk”: “”,
“clientCert”: “-----BEGIN CERTIFICATE-----\n...”,
“clientKey”: “-----BEGIN PRIVATE KEY-----\n...”
}更新日志
2026/3/18 10:56
查看所有更新日志
584fa-docs(device-register): 新增可选 tenantCode 参数说明于ece9c-docs(动态注册): 补充 psk 格式说明及 encryptionType 当前行为注意事项于3f121-docs: 修正设备动态注册签名算法描述与 Python 示例于fc2b7-docs: 补充设备动态注册 HTTP/MQTT 接口说明与示例于bddea-docs: 更新文档结构、添加新内容并优化现有文档于706e5-doc于6fa0c-doc于80b39-doc于d4fa0-doc: 更换皮肤到最新版于82830-doc于12463-feat: 支持pg于4066c-doc: 完善文档于65a22-doc: 完善文档于8794f-feat: 完善项目`于df9b4-初始化于
