Python TOTP 双因素认证实战
=============================
双因素认证(2FA)为账户增加一层安全保障。TOTP(基于时间的一次性密码)
是最流行的 2FA 方案,Google Authenticator 和 Authy 都支持此标准。
1. 安装依赖
------------
# pip install pyotp qrcode pillow
import pyotp
import qrcode
import qrcode.image.svg
import io
import base64
import time
from typing import List, Optional
2. TOTP 基础:生成密钥与验证
-----------------------------
def generate_totp_secret() -> str:
"""生成 TOTP 共享密钥(Base32 编码)"""
# pyotp.random_base32() 生成一个随机的 Base32 密钥
# 长度默认 32 字符(160 位熵)
secret = pyotp.random_base32()
return secret
def create_totp(secret: str) -> pyotp.TOTP:
"""使用密钥创建 TOTP 对象"""
# TOTP 默认使用 SHA1、30 秒时间窗口、6 位数字
return pyotp.TOTP(secret)
# 演示:生成密钥并获取当前一次性密码
secret = generate_totp_secret()
print(f"TOTP 密钥: {secret}")
totp = create_totp(secret)
current_code = totp.now() # 获取当前时间窗口的验证码
print(f"当前验证码: {current_code}")
# 等待几秒后验证(模拟用户输入)
time.sleep(1)
is_valid = totp.verify(current_code)
print(f"验证码有效: {is_valid}")
# 验证过期的验证码
expired_code = totp.at(for_time=int(time.time()) - 120) # 2 分钟前
print(f"过期验证码: {expired_code}, 有效: {totp.verify(expired_code)}")
3. TOTP 参数定制
-----------------
def create_custom_totp(secret: str) -> pyotp.TOTP:
"""创建自定义参数的 TOTP 实例"""
return pyotp.TOTP(
secret,
digits=8, # 验证码位数(默认 6)
interval=60, # 时间窗口(秒,默认 30)
digest=pyotp.SHA256 # 哈希算法(默认 SHA1)
)
custom_totp = create_custom_totp(secret)
print(f"自定义 TOTP: {custom_totp.now()} (8 位, 60 秒)")
4. HOTP(基于计数器的哈希)
---------------------------
# HOTP 使用计数器而非时间,每次验证成功后计数器递增
def create_hotp(secret: str, initial_count: int = 0) -> pyotp.HOTP:
"""创建 HOTP 实例"""
return pyotp.HOTP(secret, initial_count=initial_count)
hotp = create_hotp(secret)
# 生成第 1、2、3 个验证码
for i in range(3):
code = hotp.at(i)
print(f"HOTP 第 {i+1} 次: {code}")
# 验证时会自动使用当前计数器
print(f" 验证: {hotp.verify(code, counter=i)}")
# HOTP 适用于离线场景或无法同步时间的设备
# 但需要维护计数器同步,比 TOTP 更复杂
5. 生成二维码(用于用户绑定)
------------------------------
def generate_qr_code_uri(secret: str, username: str,
issuer: str = "MyApp") -> str:
"""生成 otpauth URI 供二维码使用"""
# 标准格式: otpauth://totp/ISSUER:USERNAME?secret=SECRET&issuer=ISSUER
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri(name=username, issuer_name=issuer)
return uri
def create_qr_code_image(uri: str) -> str:
"""生成二维码并返回 Base64 编码的 SVG"""
qr = qrcode.make(uri, image_factory=qrcode.image.svg.SvgImage)
buf = io.BytesIO()
qr.save(buf)
svg_data = buf.getvalue().decode()
# 转为 HTML 可内联的 Base64(实际使用)
b64 = base64.b64encode(buf.getvalue()).decode()
return f"data:image/svg+xml;base64,{b64}"
# 生成用户的绑定 URI
uri = generate_qr_code_uri(secret, "user@example.com", "MySecureApp")
print(f"Provisioning URI:")
print(f" {uri}")
# 二维码生成(实际部署时显示给用户扫描)
# 用户使用 Google Authenticator / Authy 扫码即可完成绑定
6. 验证流程(服务端核心逻辑)
-----------------------------
class TOTPAuthenticator:
"""TOTP 双因素认证管理器"""
def __init__(self, secret: str):
self.secret = secret
self.totp = pyotp.TOTP(secret)
# 允许前后各 1 个时间窗口的偏移(共 3 个窗口)
self.allowed_drift = 1
def verify_code(self, code: str) -> bool:
"""
验证用户输入的验证码,允许一定的时间偏移。
valid_window 参数允许前后各 N 个时间窗口的偏移
"""
return self.totp.verify(code, valid_window=self.allowed_drift)
def get_current_code(self) -> str:
"""获取当前验证码(仅用于调试或测试)"""
return self.totp.now()
# 模拟完整的验证流程
auth = TOTPAuthenticator(secret)
user_input = auth.get_current_code()
print(f"\n用户输入验证码: {user_input}")
print(f"服务端验证结果: {auth.verify_code(user_input)}")
7. 备份码(Recovery Codes)
---------------------------
def generate_backup_codes(count: int = 8) -> List[str]:
"""生成一次性备份恢复码"""
import secrets
codes = []
for _ in range(count):
# 生成 10 位十六进制编码
code = secrets.token_hex(5).upper()
# 格式化为分组形式,便于输入
formatted = f"{code[:5]}-{code[5:]}"
codes.append(formatted)
return codes
def store_backup_codes(codes: List[str]) -> List[dict]:
"""返回带哈希的备份码用于存储"""
import hashlib
stored = []
for code in codes:
# 存储哈希值而非明文(一次验证后立即销毁)
code_hash = hashlib.sha256(code.encode()).hexdigest()
stored.append({"hash": code_hash, "used": False})
return stored
# 生成 8 个备份码
backup_codes = generate_backup_codes(8)
print("\n备份恢复码(请立即保存到安全位置):")
for i, code in enumerate(backup_codes, 1):
print(f" {i}. {code}")
# 备份码应显示一次后就不可再查看
# 每个备份码只能使用一次,用过后立即作废
8. 时间偏移处理
----------------
def handle_time_drift(secret: str, user_code: str) -> dict:
"""
处理客户端与服务器之间的时间偏差。
某些手机时间可能不准确,导致验证失败。
"""
totp = pyotp.TOTP(secret)
# pyotp 的 verify 已内置 valid_window 参数
# 但可以手动检测偏量用于日志
expected_time = int(time.time())
matched = False
for offset in range(-3, 4):
check_time = expected_time + (offset * 30)
if totp.at(for_time=check_time) == user_code:
matched = True
print(f" [调试] 在偏移 {offset} 个窗口处匹配")
break
return {
"verified": matched,
"server_time": expected_time,
"drift_detected": matched
}
# 任何成功验证都可以记录当前时间偏移,用于后续优化
drift_result = handle_time_drift(secret, auth.get_current_code())
print(f"\n时间偏移检测: {drift_result}")
9. 生产环境集成注意事项
------------------------
def totp_setup_for_new_user(username: str) -> dict:
"""为新用户初始化 TOTP 的完整流程"""
# 1. 生成密钥
secret = generate_totp_secret()
# 2. 生成二维码 URI
uri = generate_qr_code_uri(secret, username)
# 3. 生成备份码
recovery_codes = generate_backup_codes(8)
# 4. 返回设置信息(实际项目中需要绑定到用户)
return {
"secret": secret, # 存入用户表的 totp_secret 字段
"qr_uri": uri, # 显示给用户扫码
"recovery_codes": recovery_codes, # 显示给用户保存
"enabled": False # 待用户验证后才启用
}
def verify_and_enable_totp(secret: str, user_code: str) -> bool:
"""用户首次设置时,验证一次以确认绑定成功"""
totp = pyotp.TOTP(secret)
return totp.verify(user_code)
# 示例流程
new_user = totp_setup_for_new_user("alice@example.com")
print(f"\n新用户 TOTP 设置完成,密钥: {new_user['secret']}")
10. 安全建议
------------
# 1. 密钥传输必须使用 HTTPS
# 2. 二维码只在首次设置时显示一次
# 3. 备份码显示后应立即存储到安全位置
# 4. 设置速率限制防止暴力枚举 6 位验证码(百万分之一概率)
# 5. 监控异常的大量验证失败尝试
# 6. 记录 TOTP 验证成功的设备信息和 IP
# 7. 支持用户撤销所有已绑定的 TOTP 设备
11. 总结
---------
TOTP 为应用提供了强大的第二层认证。pyotp 库实现简洁,与主流
验证器应用完全兼容。结合备份码和合理的时间偏移容错,可以构建
安全可靠的 2FA 系统,显著提升账户安全性。
PythonTOTP双因素认证
张小明
前端开发工程师
Harbor离线安装后,你的Docker客户端真的配好了吗?一份保姆级的证书配置与验证清单
Harbor离线安装后Docker客户端证书配置全指南:从原理到避坑实战当你终于完成Harbor的离线安装,满心欢喜地执行docker pull时,屏幕上突然跳出的"x509: certificate signed by unknown authority"错误提示,是否让你瞬间从…
2026年比话降AI率实测报告:知网论文AI率84.9%降到1.4%
这可能是 2026 年毕业季被问得最多的问题之一。随着知网 AIGC 检测系统升级,AIGC 查重率检测精度大幅提升,越来越多的学生发现自己的论文 AI 率超标。在各类降 AI 工具中,比话以 " 科学去 aigc 痕迹、承诺 15% 以下、不达标退款 " …
别再只调PID了!用一阶ESO给你的Arduino小车做个“抗干扰外挂”
别再只调PID了!用一阶ESO给你的Arduino小车做个"抗干扰外挂"当你用Arduino制作智能小车时,是否遇到过这样的困扰:明明在平地上调试好的PID参数,一旦遇到斜坡或负载变化,小车就开始"抽风"ÿ…
区块链支付通道:原理、实现与闪电网络实战解析
1. 区块链支付提速:为什么我们需要支付通道?最近在家研究区块链技术,发现一个特别有意思的领域:如何让区块链上的支付变得像刷信用卡一样快。我们都知道比特币和以太坊,它们很安全、去中心化,但一提到实际付…
精通二进制逆向分析:Detect-It-Easy实战配置与深度调优指南
精通二进制逆向分析:Detect-It-Easy实战配置与深度调优指南 【免费下载链接】Detect-It-Easy Program for determining types of files for Windows, Linux and MacOS. 项目地址: https://gitcode.com/gh_mirrors/de/Detect-It-Easy Detect-It-Easyÿ…
3D打印智能时钟:从PIR传感器到LED灯带的个性化家居制作
1. 项目概述与设计初衷几年前,我女儿房间的学习角光线一直不太理想。重新粉刷墙壁后,我不想再打孔安装壁灯破坏墙面,而她正好也需要一个时钟。作为一个经常接各种定制单子的3D打印从业者,我脑子里冒出的第一个念头就是:…