设备安全控制 key-grant
约 1359 字大约 5 分钟
2026-07-04
设备安全控制用于让 App 在发送高敏感控制指令时附带一次认证信息,设备收到指令后可以用本机密钥独立校验指令是否来自已授权 App。
当前版本的 key-grant 只负责给 App 派生和返回 App 控制 key。控制 key 不会通过 MQTT 或 HTTP 下发给设备,设备侧需要用自己的 deviceSecret 和相同派生规则得到同一把 key。
适用场景
- App 打开设备设置中的“加密控制”。
- App 对已经开启加密控制的设备发送属性控制或行为调用。
- 设备固件需要校验控制请求里的
secureAuth,避免只凭普通下发 payload 执行敏感动作。
接口
POST /api/v1/things/device/secure-control/key-grant请求需要携带登录态。服务端会校验用户对目标设备是否有控制权限。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
productID | string | 是 | 产品 ID |
deviceName | string | 是 | 设备名称 |
purpose | string | 否 | 不填默认 app-sec-ctrl-v1,当前只支持该值 |
keyVersion | number | 否 | 不填默认 1,取值范围为 1 到 uint32 最大值 |
authAlg | string | 否 | 不填默认 aes-cmac-64,当前只支持该值 |
appKeyWrapPub | string | 否 | App 一次性 X25519 公钥 Base64;传入后服务端返回包装后的 key |
示例:
{
"productID": "140",
"deviceName": "9C7F641BF30E",
"purpose": "app-sec-ctrl-v1",
"keyVersion": 1,
"authAlg": "aes-cmac-64"
}成功响应
平台接口外层仍使用统一响应结构,业务字段在 data 中。
{
"code": 200,
"msg": "success",
"data": {
"version": "app-sec-ctrl-v1",
"productID": "140",
"deviceName": "9C7F641BF30E",
"authAlg": "aes-cmac-64",
"keyVersion": 1,
"keyEncoding": "plain-base64-v1",
"appControlKey": "Base64-encoded-16-byte-key",
"scopeBits": "0x00000003",
"ttlSec": 86400
}
}字段说明:
| 字段 | 说明 |
|---|---|
version | 协议版本,固定为 app-sec-ctrl-v1 |
authAlg | 控制认证算法,当前固定为 aes-cmac-64 |
keyVersion | 本次派生使用的 key 版本 |
keyEncoding | plain-base64-v1 或 wrapped-key-v1 |
appControlKey | plain-base64-v1 时返回,16 字节 App 控制 key 的 Base64 |
wrappedKey | wrapped-key-v1 时返回,AES-256-GCM 密文和 tag 拼接后 Base64 |
wrapNonce | wrapped-key-v1 时返回,12 字节 GCM nonce 的 Base64 |
serverWrapPub | wrapped-key-v1 时返回,服务端一次性 X25519 公钥 Base64 |
scopeBits | 当前 key 的授权范围,第一版默认 0x00000003 |
ttlSec | App 本地缓存建议有效期,默认 86400 秒 |
权限规则
服务端按以下顺序处理:
- 校验
productID、deviceName、purpose、authAlg、keyVersion。 - 读取设备信息,要求设备存在且有合法
deviceSecret。 - 校验当前用户具备目标项目读写权限,或具备该设备分享的管理员权限。
- 用设备
deviceSecret派生 App 控制 key。 - 按请求返回明文 Base64 key 或包装后的 key。
权限不足时接口返回权限错误;参数不支持时返回参数错误。
密钥派生规则
服务端和设备侧使用相同规则派生 App 控制 key:
secret = base64_decode(deviceSecret)
rootKey = secret[0:16]
context = "YK_APP_SEC_CTRL_V1" || 0x00 ||
productID || 0x00 ||
deviceName || 0x00 ||
uint32_le(keyVersion)
appControlKey = AES-CMAC-128(rootKey, context)appControlKey 为 16 字节。服务端返回给 App 后,App 只在本地安全存储中保存,不应写入业务日志。
wrapped-key-v1
如果 App 请求中携带 appKeyWrapPub,服务端不返回 appControlKey,而是返回 wrapped-key-v1:
- App 生成一次性 X25519 密钥对,把公钥 Base64 放入
appKeyWrapPub。 - 服务端生成一次性 X25519 密钥对,与 App 公钥计算共享密钥。
- 服务端用
HKDF-SHA256派生 AES-256-GCM 包装 key,salt 为YK_APP_SEC_CTRL_WRAP_V1,info 为响应元数据 AAD。 - 服务端使用 12 字节随机 nonce 加密 16 字节
appControlKey。 - App 用自己的 X25519 私钥和
serverWrapPub解开wrappedKey。
wrapped 响应示例:
{
"code": 200,
"msg": "success",
"data": {
"version": "app-sec-ctrl-v1",
"productID": "140",
"deviceName": "9C7F641BF30E",
"authAlg": "aes-cmac-64",
"keyVersion": 1,
"keyEncoding": "wrapped-key-v1",
"wrappedKey": "Base64-ciphertext-and-tag",
"wrapNonce": "Base64-12-byte-nonce",
"serverWrapPub": "Base64-x25519-public-key",
"scopeBits": "0x00000003",
"ttlSec": 86400
}
}App 控制请求中的 secureAuth
App 拿到 appControlKey 后,控制请求仍走原有属性控制或行为调用接口。App 不把控制 key 发给设备,只把认证包作为 secureAuth 放入控制 payload 中。
当前实现中,secureAuth 是控制参数的一部分,例如属性控制参数中同时包含目标属性和 secureAuth:
{
"secure_control_enable": {
"enabled": true,
"keyVersion": 1
},
"secureAuth": "{\"version\":\"app-sec-ctrl-v1\",\"keyVersion\":1,\"seq\":1783145592,\"ts\":1783145592,\"expireAt\":1783145712,\"scopeBits\":\"0x00000003\",\"authAlg\":\"aes-cmac-64\",\"cmac64\":\"0FB27E25B87548E3\"}"
}设备侧需要:
- 用本机
deviceSecret派生同一把appControlKey。 - 从
appControlKey派生下行控制认证 key。 - 按 App 约定的规范化参数重新计算
cmac64。 - 校验版本、时间窗口、序列号和授权范围。
- 校验通过后再执行控制指令。
安全边界
deviceSecret不返回给 App。appControlKey或wrappedKey响应体不应记录到日志。- App 控制 key 不通过 MQTT/HTTP 发送给设备。
secureAuth是请求认证材料,不是密钥;它可以随控制 payload 到达设备。ttlSec是 App 本地缓存建议时间,设备侧仍应按secureAuth.expireAt校验单次请求有效期。
联调建议
- 先调用
key-grant,确认返回code=200且data.keyEncoding符合预期。 - App 本地保存
appControlKey后,发送带secureAuth的属性控制。 - 设备打印派生出的 key 版本、认证输入摘要和
cmac64对比结果。 - 平台侧查看控制接口返回、设备回复超时、设备属性上报和 TDengine 属性日志,区分“接口授权失败”“签名失败”和“设备未回复”。
更新日志
2026/7/4 14:31
查看所有更新日志
87a83-docs: 补充设备安全控制 key-grant 文档于
