一次性密码(OTP)是现代身份认证的基石,广泛应用于登录、密码重置和交易验证。但并非所有 OTP 都同等安全,糟糕的实现可能将安全特性变成漏洞。本文解释 HOTP 和 TOTP 的密码学原理,指出常见陷阱,并展示如何有效使用 OTP 生成器。

什么是 OTP?
一次性密码(OTP) 是一个临时、一次性使用的代码,用于验证特定会话或交易中的用户身份。与静态密码不同,OTP 快速过期且不可重复使用,因此能抵抗重放攻击。
OTP 通常通过短信、电子邮件或认证器应用传递。但其底层算法——HOTP(基于 HMAC 的一次性密码) 和 TOTP(基于时间的一次性密码)——定义了代码的生成方式。
HOTP 与 TOTP:核心算法
HOTP(RFC 4226)
HOTP 使用一个计数器,每次成功认证后递增。服务器和客户端共享一个密钥,两者计算 OTP 的方式为:
HOTP(K, C) = Truncate(HMAC-SHA-1(K, C))
其中:
K是共享密钥。C是一个 8 字节的计数器值。Truncate提取动态二进制码并转换为可配置长度的十进制 OTP(通常 6-8 位)。
优点: 可离线工作;无需时钟同步。 缺点: 如果用户生成代码但未认证(例如多次点击“下一个”),计数器会不同步。
TOTP(RFC 6238)
TOTP 将计数器替换为时间步长(通常 30 秒)。代码计算方式为:
TOTP(K, T) = HOTP(K, floor((current_time - T0) / X))
其中:
T0是 Unix 纪元(0)或自定义起始时间。X是时间步长(例如 30 秒)。
优点: 自动重新同步;无计数器漂移。 缺点: 需要精确时钟;容易受时钟偏差影响(通常允许 ±1 步长)。
| 特性 | HOTP | TOTP |
|---|---|---|
| 输入 | 计数器(递增) | 时间(当前 Unix 时间) |
| 同步方式 | 手动(计数器重同步) | 自动(基于时间) |
| 时钟要求 | 无 | 需要精确时钟 |
| 常见用途 | 硬件令牌 | 认证器应用 |
为什么 OTP 实现很重要?
一个实现良好的 OTP 系统能提供强大的多因素认证(MFA)。然而,常见错误可能使其不安全。研究(例如 OTP-Hunter,2025)已识别出四大漏洞类别:
- OTP 轰炸——向目标重复发送 OTP,造成骚扰或资源耗尽。
- OTP 暴力破解——由于缺少速率限制,枚举短 OTP 码(例如 4-6 位)。
- OTP 泄漏——在日志、URL 或 API 响应中暴露 OTP。
- 资源消耗——通过大量 OTP 生成请求导致服务器端拒绝服务。
SIM 卡劫持威胁
基于短信的 OTP 特别容易受到 SIM 卡劫持攻击,攻击者诱骗运营商将受害者的手机号码转移到新 SIM 卡。一旦控制,攻击者就能收到所有短信 OTP。这已促使菲律宾央行(BSP)等监管机构强制要求在 2026 年前淘汰短信 OTP。
常见实现陷阱
- 未对 OTP 请求进行速率限制——攻击者可淹没服务器,导致拒绝服务或暴力破解尝试。
- OTP 长度过短——4 位代码只有 10,000 种组合;若无速率限制,可在几分钟内暴力破解。
- 可预测的 OTP 种子——使用弱随机数生成器(例如
rand()而非crypto/rand)使密钥可被猜测。 - 以明文存储 OTP——数据库泄露会暴露活动代码。
- 忽略时钟偏差——TOTP 实现必须允许一个窗口(例如 ±1 时间步长)来应对时钟漂移,但窗口过大会削弱安全性。
- 无账户锁定——几次失败尝试后,应临时锁定账户以防止暴力破解。
完整示例:生成并验证 TOTP
让我们用 Python 的 pyotp 库走一遍完整示例。
第 1 步:安装库
pip install pyotp
第 2 步:生成密钥
import pyotp
# 生成随机 base32 密钥(16 字符 = 80 位)
secret = pyotp.random_base32()
print(f"Secret: {secret}")
示例输出:JBSWY3DPEHPK3PXP
第 3 步:创建 TOTP 对象并获取当前代码
totp = pyotp.TOTP(secret)
current_code = totp.now()
print(f"Current OTP: {current_code}")
第 4 步:验证代码
# 模拟用户输入
user_input = input("Enter OTP: ")
if totp.verify(user_input):
print("Valid OTP")
else:
print("Invalid OTP")
第 5 步:为认证器应用生成二维码
uri = totp.provisioning_uri(name="user@example.com", issuer_name="MyApp")
print(f"URI: {uri}")
# 使用二维码库(例如 qrcode)将此 URI 显示为二维码。
URI 遵循 otpauth:// 方案,认证器应用(Google Authenticator、Authy)可扫描。
使用 OTP 生成器工具
我们的 OTP 生成器 让你快速生成 HOTP/TOTP 代码用于测试或演示。你可以指定密钥、算法、位数和时间步长。这对于以下场景特别有用:
- 调试应用中的 OTP 集成。
- 无需搭建完整服务器即可生成测试代码。
- 理解不同参数如何影响输出。
安全最佳实践
- 大多数应用使用 TOTP 而非 HOTP——时间同步比计数器管理更容易。
- 对 OTP 生成和验证端点实施速率限制。
- 至少使用 6 位代码(推荐 8 位)。
- 设置较短的 OTP 有效期(例如 TOTP 为 30 秒,邮件 OTP 为 5 分钟)。
- 切勿以明文记录 OTP。
- 使用密码学安全随机数生成密钥。
- 在 3-5 次失败尝试后实施账户锁定。
- 考虑基于推送的认证(例如应用内 OTP)以避免 SIM 卡劫持风险。
常见问题
HOTP 和 TOTP 有什么区别?
HOTP 使用每次递增的计数器,而 TOTP 使用当前时间作为输入。TOTP 在认证器应用中更常见,因为它无需用户操作即可自动同步。
OTP 能被暴力破解吗?
可以,如果没有速率限制。6 位代码有 100 万种组合;以每秒 1000 次请求计算,约 16 分钟可破解。速率限制和账户锁定至关重要。
为什么短信 OTP 被认为不安全?
短信 OTP 容易受到 SIM 卡劫持、SS7 拦截和钓鱼攻击。监管机构正越来越多地强制要求使用应用内 OTP 或生物识别等替代方案。
OTP 的有效期应该多长?
对于 TOTP,30 秒是标准。对于邮件或短信,通常 5-10 分钟。更长的窗口会增加拦截风险。
OTP 模糊测试中的“云触发器”是什么?
云触发器是应用代码中最后一个参数(如手机号、OTP)仍未被加密或签名的函数。在此处进行模糊测试可以绕过加密和完整性检查,从而发现漏洞。