TOTP(基于时间的一次性密码)生成与验证详解

TOTP(基于时间的一次性密码)生成与验证详解

下面我将详细解释如何生成和验证TOTP,并提供完整的Python实现示例。

TOTP工作原理

  1. 共享密钥:服务端和客户端共享一个Base32编码的密钥
  2. 时间窗口:时间被分为30秒为一个单位的窗口
  3. 计数计算:当前时间窗口计数 = 当前Unix时间戳 / 30秒
  4. HMAC计算:使用共享密钥和时间窗口计数通过HMAC-SHA1算法生成哈希值
  5. 动态码生成:从哈希值中提取4-8位数字(通常6位)

Python实现TOTP生成与验证

import hmac
import hashlib
import base64
import struct
import time
import os

def generate_secret_key(length=16):
    """生成随机的Base32密钥"""
    # Base32字母表(RFC 3548)
    base32_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
    
    # 生成随机字节
    random_bytes = os.urandom(length)
    
    # 转换为Base32
    secret = base64.b32encode(random_bytes).decode('utf-8')
    
    # 移除Base32编码的填充等号
    return secret.rstrip('=')

def generate_totp(secret, timestamp=None, digits=6):
    """
    生成TOTP验证码
    
    参数:
        secret: 共享密钥(Base32编码)
        timestamp: 时间戳(默认当前时间)
        digits: 生成的验证码位数(4-8)
    
    返回:
        str: TOTP验证码
    """
    # 确保密钥长度是8的倍数(添加填充)
    secret += '=' * (8 - len(secret) % 8) if len(secret) % 8 else ''
    
    # 解码Base32密钥
    try:
        key = base64.b32decode(secret.upper())
    except:
        raise ValueError("无效的Base32密钥")
    
    # 获取当前时间戳(30秒为一个窗口)
    counter = timestamp or int(time.time())
    counter = counter // 30
    
    # 将计数器打包为8字节的大端字节序
    counter_bytes = struct.pack(">Q", counter)
    
    # 使用HMAC-SHA1计算哈希值
    hmac_hash = hmac.new(key, counter_bytes, hashlib.sha1).digest()
    
    # 动态截断法(Dynamic Truncation)
    offset = hmac_hash[-1] & 0x0F
    dynamic_code = (
        (hmac_hash[offset] & 0x7F) << 24 
        | (hmac_hash[offset+1] & 0xFF) << 16 
        | (hmac_hash[offset+2] & 0xFF) << 8 
        | (hmac_hash[offset+3] & 0xFF)
    )
    
    # 限制位数
    code = dynamic_code % (10 ** digits)
    
    # 格式化为固定位数
    return str(code).zfill(digits)

def verify_totp(secret, code, digits=6):
    """
    验证TOTP验证码
    
    参数:
        secret: 共享密钥(Base32编码)
        code: 用户提供的验证码
        digits: 验证码位数
    
    返回:
        bool: 验证是否通过
    """
    # 考虑到时间漂移,检查前后一个时间窗口(总共3个窗口)
    timestamp = int(time.time())
    
    # 当前时间窗口
    if generate_totp(secret, timestamp, digits) == code:
        return True
    
    # 前一个时间窗口(30秒前)
    if generate_totp(secret, timestamp - 30, digits) == code:
        return True
    
    # 后一个时间窗口(30秒后)
    if generate_totp(secret, timestamp + 30, digits) == code:
        return True
    
    return False

if __name__ == "__main__":
    # 生成共享密钥
    secret_key = generate_secret_key()
    print(f"共享密钥: {secret_key}")
    
    # 生成TOTP验证码
    totp_code = generate_totp(secret_key)
    print(f"当前TOTP验证码: {totp_code}")
    
    # 验证TOTP验证码
    print("\n验证测试:")
    print(f"验证正确代码: {verify_totp(secret_key, totp_code)}")  # 应返回True
    print(f"验证错误代码: {verify_totp(secret_key, '123456')}")   # 应返回False
    
    # 模拟时间窗口切换
    print("\n时间窗口切换测试:")
    print("等待时间窗口切换...")
    time.sleep(31)  # 等待超过30秒以便切换到下一个时间窗口
    new_totp_code = generate_totp(secret_key)
    print(f"新TOTP验证码: {new_totp_code}")
    print(f"验证过期代码: {verify_totp(secret_key, totp_code)}")  # 应返回False
    print(f"验证最新代码: {verify_totp(secret_key, new_totp_code)}")  # 应返回True
    print(f"验证上一个窗口代码: {verify_totp(secret_key, totp_code, timestamp=int(time.time())-30)}")  # 应返回True

代码说明

1. 生成共享密钥

  • 使用安全的随机数生成器(os.urandom)创建随机字节
  • 使用Base32编码转换为可打印字符串
  • 移除Base32的填充等号使密钥更简洁

2. 生成TOTP验证码

  1. 准备密钥

    • 确保密钥长度是8的倍数(添加等号填充)
    • 使用base64.b32decode解码Base32密钥
  2. 时间窗口计算

    • 获取当前Unix时间戳
    • 除以30得到时间窗口计数
  3. HMAC-SHA1计算

    • 使用HMAC算法结合密钥和时间窗口计数生成哈希值
    • HMAC提供消息完整性验证和身份验证
  4. 动态截断法

    • 从哈希值中提取4字节的动态代码
    • 使用最后一个字节的低4位作为偏移量
  5. 生成数字验证码

    • 将动态代码转换为数字
    • 截取指定位数(默认6位)
    • 添加前导零使长度固定

3. 验证TOTP验证码

  • 考虑时间漂移问题,检查三个连续时间窗口:
    1. 当前时间窗口
    2. 前一个时间窗口(30秒前)
    3. 后一个时间窗口(30秒后)
  • 如果任一窗口生成的验证码匹配用户输入,则验证通过

TOTP最佳实践

  1. 密钥安全

    • 使用强随机密钥(至少128位)
    • 安全存储密钥(加密存储)
    • 避免在日志或客户端暴露密钥
  2. 用户体验

    • 使用6位验证码(足够安全且便于用户输入)
    • 提供清晰的倒计时提示
    • 支持恢复密钥机制
  3. 安全增强

    • 限制验证尝试次数(防止暴力破解)
    • 实施账户锁定策略
    • 记录所有验证尝试
  4. 与标准兼容

    • 遵循RFC 6238(TOTP)和RFC 4226(HOTP)标准
    • 支持Google Authenticator等通用应用

使用pyotp库的简化版本

import pyotp
import time

# 生成共享密钥
secret = pyotp.random_base32()
print(f"共享密钥: {secret}")

# 创建TOTP对象
totp = pyotp.TOTP(secret, interval=30, digits=6)

# 生成当前验证码
current_code = totp.now()
print(f"当前TOTP验证码: {current_code}")

# 验证验证码
print(f"验证结果: {totp.verify(current_code)}")

# 生成Provisioning URI (用于生成二维码)
uri = totp.provisioning_uri("user@example.com", issuer_name="MyApp")
print(f"\nProvisioning URI:\n{uri}")

pyotp库提供了更简化的接口,同时保持了与标准兼容性,并支持生成用于认证器应用的URI。

总结

TOTP是一种强大的双因素认证机制,通过结合时间因素和共享密钥提供额外的安全层。实现时需要注意密钥安全、时间同步和用户体验等问题。对于生产环境,建议使用经过充分测试的库如pyotp,同时结合数据库安全存储用户密钥。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值