2FA 与 OTP
最后更新于
最后更新于
核心原理 OTP 算法基于共享密钥(Secret Key)和动态因子(如时间或计数器)生成一次性密码。算法流程如下:
首选计算 HMAC = HMAC_SHA(Secret Key, Dynamic Factor)
TOTP (Time-Based One-Time Password Algorithm)
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
图片来源:https://notes.mengxin.science/2017/05/30/hotp-totp-algorithm-analysis/
TOTP 算法作为 HOTP 算法的拓展算法
图片来源:https://notes.mengxin.science/2017/05/30/hotp-totp-algorithm-analysis/
图片来源:https://notes.mengxin.science/2017/05/30/hotp-totp-algorithm-analysis/
同步 counter 步骤
生成 OTP(客户端和服务器端的共同步骤) 客户端请求 OTP 时,使用当前 counter(开始时为 0 或其他初始化值)来生成 OTP。 客户端生成 OTP 后,将 counter 和 OTP 一起发送到服务器。
服务器验证 OTP 服务器接收到 OTP 和 counter 后,检查其与预期的 OTP 是否匹配。 如果匹配,则验证成功,递增 counter 并处理该请求。 如果不匹配,服务器可以尝试通过增加或减少 counter 的值(例如,counter + 1, counter - 1)进行验证,以容忍可能的同步误差。
误差窗口 服务器可能会有一个误差窗口(例如,counter = N, counter = N-1, counter = N+1 等),允许一定范围内的计数器误差。 通过验证多个可能的计数器值,系统可以容忍一些同步问题,确保 OTP 校验的准确性。
客户端和服务器的同步 如果服务器校验失败,并且未能在误差窗口内找到有效的 OTP,客户端需要重新生成 OTP 或者请求新的 OTP。 确保客户端和服务器都更新并同步 counter,防止 OTP 重复使用。
TOTP 的核心公式是将时间戳(秒级)通过除法离散化:
counter = current_time // time_step
current_time 是从 Unix 纪元(1970-01-01 00:00:00 UTC)起的当前时间戳(以秒为单位)。
time_step 是固定的时间间隔,通常为 30 秒。
假设时间戳为 1,时间步长为 30 秒:
在第 1 到 29 时间步内,客户端和服务器的计数器值都是 0 。
问题1:时间不同步导致校验失败
如果客户端和服务器的时间有偏差(例如 5 秒偏差),生成的 OTP 会基于不同的 counter,从而导致校验失败。 客户端在时间戳 29 时生成的 OTP,服务器在时间戳 34 校验时可能失败,因为它会只验证计数器 1,而不是计数器 0。
问题2:严格绑定时间步导致用户体验差 用户需要在单一时间步内使用 OTP。如果用户生成 OTP(如在时间戳 29)后,延迟输入到服务器(如在时间戳 31),OTP 校验就会失败。 这对于实际操作来说过于严格,容易让用户体验变差。
解决方案1:引入时间窗口 在 verify_totp 函数中,通过在校验时检查当前时间步的前后时间步(T-1, T, T+1),可以引入时间窗口容忍。这是 TOTP 标准推荐的做法。
解决方案2:显式 counter 校验 通过显式传递 counter 的方式,客户端和服务器可以协商如何处理时间步,例如允许客户端显式发送自己生成 OTP 时使用的计数器值,服务器据此判断是否接近当前时间步,从而进行更宽松的校验。
二维码识别内容:otpauth://totp/JumpServer:admin?secret=LZEKNECB4XSNKUD4&issuer=JumpServer