TOTP算法生成OTP时而正确时而错误的原因及修正方案
问题原因与修正方案
问题原因
未遵循TOTP规范的掩码要求
RFC 6238(TOTP标准)明确规定,从HMAC结果中提取的32位整数需要与0x7FFFFFFF进行按位与运算,清除最高位以避免有符号/无符号整数处理差异导致的结果偏差。你的代码缺少这一步,当提取的32位整数最高位为1时,生成的OTP会与遵循规范的第三方工具结果不一致,这就是“有时正确有时错误”的核心原因。主循环时间处理存在误差
通过time.sleep(wait_time)后调用int(time.time())获取时间,可能因系统调度延迟导致实际时间跨到下一个时间步,偶尔生成不属于当前显示周期的OTP,进一步加剧结果不一致的情况。
修正后的代码
import hmac import hashlib import struct import time import base64 def generate_totp(secret, time_step=30, digits=6, current_time=None): if current_time is None: current_time = int(time.time()) # 计算时间步(TOTP的核心时间窗口) time_window = current_time // time_step time_bytes = struct.pack('>Q', time_window) # 解码Base32密钥 secret = base64.b32decode(secret, casefold=True) # 计算HMAC-SHA1 hmac_result = hmac.new(secret, time_bytes, hashlib.sha1).digest() # 获取偏移量 offset = hmac_result[-1] & 0xF truncated_hash = hmac_result[offset : offset + 4] # 解压为无符号整数,并添加掩码(符合RFC 6238要求) otp = struct.unpack('>I', truncated_hash)[0] otp = otp & 0x7FFFFFFF # 关键修复:清除最高位 # 生成指定位数的OTP otp = otp % (10 ** digits) otp_str = str(otp).zfill(digits) return otp_str, time_window def get_time_until_next_step(time_step=30): current_time = int(time.time()) wait_time = time_step - (current_time % time_step) # 返回等待时间和下一个时间步的起始时间 next_window_start = current_time + wait_time return wait_time, next_window_start # Example Usage: if __name__ == "__main__": secret_key = "2FASTEST" while True: wait_time, next_window_time = get_time_until_next_step() time.sleep(wait_time) # 使用预计算的时间步起始时间,避免sleep延迟误差 current_totp, current_window = generate_totp(secret_key, current_time=next_window_time) print(f"Generated TOTP: {current_totp}")
关键修改说明
- 添加掩码运算:新增
otp = otp & 0x7FFFFFFF,严格遵循RFC 6238规范,确保生成的OTP与第三方工具完全一致。 - 优化时间步计算:
get_time_until_next_step返回下一个时间步的起始时间,直接用该时间调用generate_totp,避免sleep延迟导致的时间偏差,保证每次生成的OTP都对应正确的时间窗口。
内容的提问来源于stack exchange,提问作者Abhishek
相关产品推荐
相关产品推荐

