Skip to content

加密与签名

本文档说明蓝牙通信协议中的密钥体系、加密机制和签名计算方法。

密钥体系

协议使用3个SM4密钥(各16字节)建立安全通信:

密钥英文获取命令用途
公钥PublicKey0x3090计算AuthCode(场景1)
私钥PrivateKey0x3091SM4私钥模式加密通信
签名密钥SignKey0x3091计算MD5签名验证

注意

  • 所有密钥均为SM4密钥,不是RSA密钥
  • 0x3091命令返回PrivateKey和SignKey两个密钥
  • 密钥获取的详细说明见 设备管理

密钥获取流程

加密类型

协议支持多种加密方式,通过数据包的包标识字段低4位指定:

加密类型说明使用场景
0明文,不加密获取公钥(0x3090)
1AES128加密(保留)
2SM4公钥模式获取私钥和签名密钥(0x3091)
3SM4私钥模式其他所有命令

包标识字段

包标识字段为1字节:

  • 高4位: 表示包版本
  • 低4位: 指示数据加密类型

SM4加密模式详解

模式2: SM4公钥模式

  • 加密密钥: LockID[0:16] (锁ID的前16字节,不是PublicKey
  • 用途: 0x3091命令请求数据加密
  • 算法: SM4-ECB

命名误导

"SM4公钥模式"的密钥是LockID,与PublicKey无关。

关于"公钥"命名的历史说明

协议中"公钥"(PublicKey)和"SM4公钥模式"的命名存在历史遗留问题,容易造成误解。

命名与实际用途的差异

名称字面理解实际用途
PublicKeySM4公钥模式的密钥仅用于计算AuthCode(场景1)
SM4公钥模式使用PublicKey加密使用LockID[0:16]加密

为什么会这样

早期协议设计时,"公钥"和"SM4公钥模式"的命名可能基于某种关联设想,但最终实现中:

  • PublicKey 仅参与AuthCode的MD5计算,不作为任何加密密钥
  • SM4公钥模式 的加密密钥是LockID的前16字节,与PublicKey完全无关

为什么不修正命名

由于历史原因,这些名称已广泛使用于:

  • 现有实现代码的变量名和函数名
  • 数据库字段定义
  • 已部署设备的固件
  • 第三方集成文档

修改命名会导致大量兼容性问题,因此保留原有命名,仅在文档中说明实际含义。

正确理解方式

术语应理解为
PublicKey用于初始认证的密钥(参与AuthCode计算)
SM4公钥模式使用LockID作为密钥的SM4加密模式
SM4私钥模式使用PrivateKey作为密钥的SM4加密模式

模式3: SM4私钥模式

  • 加密密钥: PrivateKey (获取到的私钥)
  • 用途: 所有其他命令的加密通信
  • 算法: SM4-ECB

动态口令(Token)

Token 是门锁分配给用户的动态口令,用于计算 AuthCode 进行身份验证。

工作流程

  1. 首次请求:APP 发送指令时 Token 填当前时间戳
  2. 设备响应:门锁返回校验失败,响应中携带分配的 Token
  3. 二次请求:APP 使用该 Token 计算 AuthCode,再次发送请求

特性

  • Token 与用户关联
  • 每次指令执行后 Token 会刷新
  • Token 用于计算 AuthCode,防止重放攻击

AuthCode签名计算

由于锁的计算能力和内存空间有限,参与MD5计算的源数据不能太长。协议定义了两种MD5签名计算方法。

关于第一个参数

AuthCode公式中的第一个参数(AuthUserID、KeyID等),实际值只需与指令数据包中LockID字段的值一致即可。门锁校验时仅比对MD5结果,不关心参数名称。

字节拼接规则

MD5计算的是字节序列:

  • 字符串:直接转为字节(UTF-8编码)
  • 整型:转为大端字节序(4字节)
  • 密钥:二进制字节直接拼接

所有字段按顺序拼接后计算MD5。

场景1: 使用公钥计算的签名

  • 用于获取私钥(0x3091)指令
  • 用于添加用户(0x3001)指令
  • 用于删除用户(0x3002)指令
  • 用于恢复出厂设置(0x3004)指令

计算公式

AuthCode = MD5(AuthUserID + KeyID + Token + PublicKey)

字段说明

字段类型长度说明
AuthUserID字符串实际长度需要和指令中相同
KeyID字符串实际长度需要和指令中相同
Token整型4见下方说明
PublicKey二进制16公钥(由0x3090获取)

Token字段说明

  • 0x3091指令:Token 为时间戳(Timestamp),大端字节序
  • 0x3001、0x3002指令:Token 为动态口令,大端字节序
  • 0x3004指令:AuthCode = MD5(LockID + Token + PublicKey)
示例代码
c
// 拼接源数据: AuthUserID + KeyID + Token + PublicKey
uint8_t source[256];
int offset = 0;

memcpy(source + offset, authUserID, strlen(authUserID));
offset += strlen(authUserID);

memcpy(source + offset, keyID, strlen(keyID));
offset += strlen(keyID);

// Token转大端字节序
uint8_t tokenBytes[4];
tokenBytes[0] = (token >> 24) & 0xFF;
tokenBytes[1] = (token >> 16) & 0xFF;
tokenBytes[2] = (token >> 8) & 0xFF;
tokenBytes[3] = token & 0xFF;
memcpy(source + offset, tokenBytes, 4);
offset += 4;

memcpy(source + offset, publicKey, 16);
offset += 16;

// 计算MD5
uint8_t authCode[16];
md5_calculate(source, offset, authCode);

场景2: 使用签名密钥计算的签名

用于场景1以外所有需要AuthCode的指令,验证APP与门锁之间操作命令的身份合法性。

计算公式

MD5签名 = MD5(KeyID + UserID + Token + SignKey)

字段说明

字段类型长度说明
KeyID字符串实际长度需要和指令中相同
UserID字符串实际长度需要和指令中相同
Token整型4动态口令,大端字节序
SignKey二进制16签名密钥(由0x3091获取)
示例代码
c
// 拼接源数据: KeyID + UserID + Token + SignKey
uint8_t source[256];
int offset = 0;

memcpy(source + offset, keyID, strlen(keyID));
offset += strlen(keyID);

memcpy(source + offset, userID, strlen(userID));
offset += strlen(userID);

// Token转大端字节序
uint8_t tokenBytes[4];
tokenBytes[0] = (token >> 24) & 0xFF;
tokenBytes[1] = (token >> 16) & 0xFF;
tokenBytes[2] = (token >> 8) & 0xFF;
tokenBytes[3] = token & 0xFF;
memcpy(source + offset, tokenBytes, 4);
offset += 4;

memcpy(source + offset, signKey, 16);
offset += 16;

// 计算MD5
uint8_t md5Signature[16];
md5_calculate(source, offset, md5Signature);

鑫泓佳智能硬件通信协议文档