Skip to content

离线密码

离线密码是由服务端/APP 通过算法生成的一次性或限时密码,门锁无需联网即可验证。

与普通密码的区别

特性普通密码离线密码
生成方用户自定义服务端/APP 算法生成
存储时机添加时存储首次使用时存储
联网要求添加时需联网生成和使用均无需联网
用户关联添加时指定生成时已关联,使用时同步

工作原理

为什么门锁不知道用户?

离线密码的设计目标是"无需联网"。门锁验证密码时只能解析出类型和有效期,无法得知用户信息(否则密码会变得很长)。用户关联信息存储在服务端,待联网同步时补全。

需求规格

密码类型

类型参数长度典型场景
永久密码槽位 ID(1-16)6 位常住人员
单次密码截止时间6 位物业帮忙关窗、快递
限时密码开始时间 + 时长8 位维修工、假期宠物代管、亲人短住
时段密码开始 + 时长 + 截止时间9 位限定时间段访问
循环密码周几 + 截止时间 + 有效期10 位清洁工、保安巡逻
清空码新密钥种子14 位统一清空所有离线密码

约束条件

项目要求
字符集纯数字 0-9,无前导零(6 位密码范围 100000-999999)
密码长度6 位起步,按类型递增
时间覆盖20 年
验证成本MCU 友好(1-2 次 HMAC)
离线时长允许永不联网

密钥管理

  • 主密钥:设备初始化时生成,16 字节
  • 清空码:携带密钥种子生成新密钥
  • 清空效果:更换密钥后,所有旧离线密码自动失效

设计方案

时间基准

Epoch = 2025-01-01 00:00:00 UTC (Unix: 1735689600)
时间单位用途说明
小时单次/限时密码支持高精度场景
循环/时段密码节省位数

类型编码

使用 3 bit 编码类型,支持 8 种(预留 2 种扩展):

类型密码长度可用 bit
0永久密码6 位~19.8
1单次密码6 位~19.8
2限时密码8 位~26.5
3循环密码10 位~33.2
4时段密码9 位~29.7
5清空码14 位~46.5
6-7保留--

密码结构

┌─────────┬──────────────┬──────────┐
│ 类型    │ 类型特定参数  │ HMAC签名  │
│ 3 bit   │   可变长     │  剩余位   │
└─────────┴──────────────┴──────────┘

各类型详细编码

永久密码(6 位)

用于常住人员,长期有效。

[类型 3bit][槽位 4bit][签名 12bit] = 19 bit

槽位:0-15,对应槽位 1-16

槽位机制

门锁最多支持 16 个永久密码,每个槽位对应一个固定密码。删除某槽位后可重新生成该槽位的新密码,新密码与旧密码不同。

单次密码(6 位)

用于临时访问,仅可使用一次。

[类型 3bit][有效时长 8bit][签名 8bit] = 19 bit

有效时长:1-255 小时(约 10 天),0 表示当天有效

时间验证

门锁首次验证通过时记录使用时间,后续验证检查是否超过有效时长。"单次"特性通过门锁记录已使用密码实现。

限时密码(8 位)

用于指定时间段内无限次使用。

[类型 3bit][开始日 9bit][时长 4bit][签名 10bit] = 26 bit

开始日:相对 Epoch 天数 mod 512
时长:指数编码,见下表

512 天周期机制

开始日使用 9 bit 存储,取模 512 形成约 1.4 年的周期。验证时门锁在当前日期 ±256 天范围内匹配,确保任意时间点都能找到唯一对应的开始日。由于密码最长有效期为 365 天,不会出现周期重叠导致的误匹配。

时长编码表:

时长时长
01 天83 周
12 天94 周
23 天106 周
34 天112 月
45 天123 月
56 天136 月
61 周1412 月
72 周15永久

循环密码(10 位)

用于周期性访问,如每周固定时间。

[类型 3bit][周几 7bit][结束时 5bit][有效期 8bit][签名 10bit] = 33 bit

周几:7 bit 位掩码,支持任意组合
结束时:0-23 点,每天 00:00 至该小时结束前有效
有效期:0-255 天,从首次使用开始计算

周几位掩码:

bit含义示例组合
bit0周一0000001 = 周一
bit1周二0010100 = 周三和周五
bit2周三0011111 = 工作日
bit3周四1100000 = 周末
bit4周五1111111 = 每天
bit5周六
bit6周日

时段密码(9 位)

限时密码 + 每日时段限制。

[类型 3bit][开始日 9bit][时长 4bit][结束时 5bit][签名 8bit] = 29 bit

开始日:相对 Epoch 天数 mod 512
时长:同限时密码编码表
结束时:0-23 点,每天 00:00 至该小时结束前有效

清空码(14 位)

输入后清空所有离线密码,更换密钥。

[类型 3bit][密钥种子 33bit][签名 10bit] = 46 bit

密钥种子:33 bit 随机数
新密钥 = SHA256(设备ID || 旧密钥 || 种子)[0:16]

均匀分布

为避免密码有明显规律,使用密钥派生的混淆值:

混淆值 = HMAC(密钥, "obfuscate" || 密码长度)[0:n] mod 密码空间
最终密码 = (原始值 + 混淆值) mod 密码空间 + 最小值
  • 每个门锁、每种长度的混淆值固定
  • 验证时先减去混淆值还原原始值
  • 成本:初始化时计算一次

验证流程

长度与类型对应关系:

长度允许的类型值
6 位0(永久)、1(单次)
8 位2(限时)
9 位4(时段)
10 位3(循环)
14 位5(清空码)

验证成本:1 次 HMAC

安全分析

攻击者视角:盲猜 N 位数字,不知道类型和混淆算法。

碰撞概率(基于签名位数,经代码验证):

长度密码空间包含类型签名位数碰撞率
6 位90 万永久、单次8-12 bit~0.02%
8 位9000 万限时10 bit~0.01%
9 位9 亿时段8 bit~0.03%
10 位90 亿循环10 bit~0.01%
14 位90 万亿清空码10 bit~0.007%

暴力破解时间估算(门锁 5 次错误锁定 3 分钟,每小时最多 100 次):

长度碰撞率预期破解时间
6 位~1/5000~50 小时(2.1 天)
8 位~1/10000~100 小时(4.2 天)
9 位~1/3300~33 小时(1.4 天)
10 位~1/10000~100 小时(4.2 天)
14 位~1/14000~140 小时(5.8 天)

安全性说明

  1. 攻击者盲猜:不知道密码长度、类型、混淆算法
  2. 设备防护:设备端应增加防穷举机制增强安全性
  3. 时效性:密码过期后自动失效
  4. 清空码特殊:虽然碰撞率较高,但误触发只会重置密钥,不会开门

测试验证

验证测试(每种类型 10000 次生成并验证):全部通过。

碰撞测试(每种长度随机生成 10 万次密码验证碰撞率):

按照门锁错误锁定时长规则,完成该碰撞需约 1000 小时

长度测试次数碰撞次数碰撞率
6 位100000210.021%
8 位100000140.014%
9 位100000330.033%
10 位10000090.009%
14 位10000070.007%

测试脚本:offline_password_test.pyoffline_password_collision.py

实现算法

算法使用 HMAC-SHA256 进行签名计算,通过密钥派生的混淆值实现均匀分布。

参考实现:offline_password.py

测试样本

以下测试样本可用于验证算法实现的正确性。

密钥0123456789abcdef0123456789abcdef

永久密码(6位)

槽位密码
1321857
2325432
3331720
4334911
5338029
6343342
7345827
8351958
9353717
10360273

单次密码(6位)

有效时长(小时)密码
1386222
2386650
4387170
8388088
12389043
24392234
48398399
72404439
168429001
255451456

限时密码(8位)

开始日时长索引密码
0011316542
1111333910
7211433686
30311811456
60412303746
90512796105
100612961674
150713781728
200814601504
256915520393

循环密码(10位)

周几(位掩码)结束时有效期(天)密码
000000118304976363810
000001020604985307791
000010012905000018325
0001000171205034914271
0010000191505102578387
0100000221805237612864
1000000102005502923388
0011111182555228252842
1100000231005774664473
11111110506028630226

时段密码(9位)

开始日时长索引结束时密码
0018808174043
1120808313848
7212809106328
30317812130625
60419816071460
90522820012613
100610821328280
15078827889793
200823834455198
25690841797716

清空码(14位)

密钥种子密码
012415319820349
112415319821475
10012415319922324
100012415320843743
1000012415330060546
10000012415422220161
100000012416343819643
858993459121211412841120
429496729616813366330675
4611840029115660096607340

生成脚本:offline_password_sample.py

局限性与建议

安全强度权衡

离线密码算法为了保持密码长度在可接受范围内(6-14位纯数字),在签名位数上做出了牺牲,导致理论碰撞率相对较高。

必须的防护措施:

门锁端必须实现高频尝试限时锁定机制,例如:

  • 5次错误后锁定3分钟
  • 连续多次锁定后延长锁定时间(如10分钟、30分钟)
  • 记录异常尝试并在联网时上报

安全警告

离线密码算法的安全性严重依赖门锁端的防暴力破解机制。如果门锁不限制错误次数,6位密码可在短时间内被暴力破解。

时间同步要求

所有时效性密码(单次、限时、循环、时段)依赖门锁的准确时间。

处理建议:

  1. 门锁端硬件

    • 使用高精度 RTC 芯片,减少时间漂移
    • 每次联网时同步校准时间
    • 电池低电量时提醒用户更换,避免断电导致时间丢失
  2. 门锁验证时容差

    为应对门锁时间轻微偏移,建议在算法验证通过后,根据密码类型实现时间容差:

    • 限时密码/时段密码:在开始日期 ±256 天范围内找到最接近的匹配日,允许 ±1小时 偏移
    • 循环密码:检查周几匹配时考虑跨天边界,允许 ±1小时 偏移
    • 单次密码:在有效时长基础上延长 1小时
  3. 服务端/APP

    • 向用户展示密码的生效时间和过期时间
    • 提供密码失效后的重新生成功能
    • 提示用户定期让门锁联网校准时间
    • 更换电池后提醒用户及时联网同步时间

时间覆盖范围

当前设计的时间基准为 2025-01-01 00:00:00 UTC,支持约 20 年时间范围。

说明:

  • 这是为了节省密码位数做出的工程权衡
  • 20年时间范围足够覆盖产品全生命周期
  • 如需延长,可在固件更新时调整 Epoch 基准点

密码撤销限制

离线密码无法单独撤销,只能等待自然过期或使用清空码全部清空。

建议的黑名单机制:

门锁端和服务端配合实现黑名单功能以应对密码泄露:

  • 门锁端:先检查黑名单,再进行算法验证;黑名单中的密码直接拒绝
  • 服务端:记录黑名单密码,避免重复生成

永久密码槽位管理

永久密码基于槽位ID(1-16)和密钥生成,相同槽位生成的密码固定不变。

槽位删除与重新生成:

删除某槽位的永久密码后,如需重新使用该槽位:

  • 服务端:重新生成该槽位密码时,需通知门锁清除旧密码的黑名单记录
  • 门锁端:收到槽位重置指令后,清除该槽位旧密码的黑名单记录

否则新生成的密码与旧密码相同,会被黑名单阻止使用。

与其他密码机制的优先级

离线密码使用纯数字,可能与其他密码机制(如用户自定义密码)生成相同的数字串。

建议的验证优先级:

1. 用户自定义密码(基于数据库存储)
2. 管理员密码
3. 离线密码(算法验证)
4. 其他密码类型

原因:

  • 离线密码空间大(90万至90万亿),碰撞概率极低但非零
  • 基于存储的密码可精确匹配,应优先验证
  • 避免用户自定义密码被误识别为离线密码导致的权限混乱

实现建议:

  • 门锁端按优先级顺序验证密码
  • 一旦某层级验证通过,停止后续验证
  • 记录验证通过的密码类型,便于审计

物联网设备通信协议文档