一日一技:如何实现临时密码?

我买的房子今天交房了。开发商配的门锁是某品牌的智能门锁,它可以使用指纹开锁,也可以使用密码开锁。在使用手机跟门锁配对以后,可以远程在手机上生成临时密码。临时密码只能使用1次,并且在生成的30分钟内有效。这个功能可以方便装修人员进出又不用担心泄露密码。

因为新房子还没有通网,所以门锁肯定是无法连接互联网的。而装修人员给我打电话要临时密码时,我在公司,离家几十公里外,门锁也不可能跟手机通信。

那么问题来了,门锁是怎么验证这个临时密码合法的?

今天我一直在想这个问题,目前有一些思路,但无法确定。所以发出来跟大家一起讨论一下它的实现方法。

已知:

  1. 手机App只有第一次跟门锁配对时,会通信,之后就完全不会有任何通信
  2. 门锁无法连接外网
  3. 无论我在任何地方,手机上都能生成临时密码。门锁输入临时密码就能解锁
  4. 临时密码只能使用一次,之后就会失效
  5. 临时密码是8位数字
  6. 临时密码有效期30分钟,超时以后就会失效
  7. 手机可以连续多次生成临时密码,每一次密码都不一样,但每个临时密码都可以使用

首先第4条非常简单,在门锁里面记录一下已经使用的密码就可以实现密码只能使用1次。所以不需要考虑这个问题了。

另外几个问题,我根据我自己的编程经验做一些推测。

临时密码是一个8位数字,例如8031 1257。由于手机不需要跟门锁通信,门锁就能够识别这个密码,因此我一开始觉得这个8位数字包含某种校验规则。例如,前4个数字,乘以100以后对26取余数,就是第5、6位数字。前6个数乘以5643然后对97取余数,就是第7、8位数字。这里的四个关键数字10026564397,可能是手机在和门锁配对的时候发送给门锁的。

但这里无法解释门锁怎么知道数字什么时候过期。难道8位数字能够包含精确到分钟的时间戳信息?例如现在我写文章的时候,对应的时间戳是1740143928。这是一个10位数字,我实在想不到如何把一个10位的数字藏在8位数字里面,并且在必要的时候还能还原回原来的10位数字。

因此,我换了一个思路,有没有可能密码锁里面自带一个时钟?在手机配对时,会同步校准这个时钟,使它跟手机保持相同的时间。如果是这种方案,那么这个临时密码8位数字,其实可以不用包含自我校验。我们可以使用app和密码锁各自按相同的逻辑走一轮加密,然后对比生成的密钥是否相同。

手机在跟密码锁配对时,发送一个密钥到密码锁里面。要生成临时密码时,手机使用时间戳和密钥通过某个算法生成8个数字。密码锁也使用时间戳和这个相同的密钥,相同的算法,也生成8位数字,如果跟临时密码相同,就开锁。对应的Python密码类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import hashlib
import time


SECRET_KEY = 123456 # 手机同步给密码锁的密钥
now = int(time.time())
timespan = now // 1800 # 在30分钟内,这个值都是相同的

temp_key_hex = hashlib.md5(str(SECRET_KEY + timespan).encode()).hexdigest() # 16进制的密码
temp_key_full = int(temp_key_hex, 16) # 转成10进制

temp_key = str(temp_key_full)[-8:] # 取最后8位数字
print('临时密码:', temp_key)

运行效果如下图所示:

由于从任何时间戳x开始,x // 1800 == (x + n) // 1800x // 1800 + 1 == (x + n) // 1800,其中0 <= n <= 1800。密码锁使用相同的代码,只不过分别把timespantimespan + 1都生成一个密码,然后对比,这样就可以实现手机生成密码过几分钟再在锁上面输入密码,锁也能成功验证。

使用这种方式,可以满足编号1-6的需求。但问题是遇到需求7怎么办?上面这种方式,会导致在30分钟内,临时密码只有这一个。

我绞尽脑汁想不出一个聪明的算法能解决这个问题。但是我在多次尝试生成临时密码时,发现两次密码的生成间隔必须大于5秒。

如果它的算法真的那么聪明,为什么不能让我生成无限个可用的临时密码?所以我怀疑它可能没有那么聪明,它可能用的是笨办法,例如:穷举。

对手机来说,生成密码的算法跟上面差不多,唯一的区别是,把其中的1800换成5。也就是说,每5秒钟会生成不同的临时密码。因为now // 5在5秒钟内是相同的,超过5秒就会变。

而对于门锁,当它感知到用户正在输入密码时,以当前时间戳为起点,每5秒一轮,往前推360轮,生成360个密码。如果发现用户输入的8位数跟这360个密码中的某一个相同,就解锁。

如果锁的芯片性能好,在用户按完临时密码前,就能够做完360次计算,用户完全不会有任何感知。如果锁的芯片不够好,它可以在内存中维持一个长度为360的双向链表,每5秒计算一个密码放进去,然后把末尾的密码丢掉。这样当用户输入密码的时候,它可以直接跟这个双向链表中的密码逐一比对。

当然,以上全都是我个人的推测。如果大家知道它是怎么做的,或者想到了什么更好的方法,欢迎留言一起交流。