正在加载,请稍候…

OTP 安全:从短信到应用内令牌的演进与最佳实践

深入分析短信 OTP 的 SIM 卡劫持、SS7 拦截和钓鱼攻击,对比应用内 OTP 的安全性,提供实现与防护的最佳实践。

一次性密码(OTP)无处不在:登录、密码重置、支付确认。它们看似简单——一串发到你手机上的短码——但其安全性严重依赖于传输渠道。短信 OTP 曾是黄金标准,如今却成为主要攻击面。本文深入分析基于短信的 OTP 的脆弱性,解释为何应用内 OTP 正获得青睐,并为开发者提供具体的最佳实践。你可以使用我们的 OTP 生成器 生成测试 OTP,直观感受不同实现的差异。

手持显示短信验证码的手机的人

针对短信 OTP 的三种主要攻击

短信 OTP 依赖于“你的手机号 = 你”的假设。攻击者有三种行之有效的方法打破这一假设:

SIM 卡劫持

攻击者通过贿赂或伪造证件说服运营商将你的手机号转移到他们控制的 SIM 卡上。此后,你所有的短信——包括 OTP——都会发送到他们的设备。这种攻击在运营商验证薄弱的地区尤为常见。

SS7 拦截

七号信令系统(SS7)是电信网络间通信的协议,设计于几十年前,安全性极低。攻击者可以利用 SS7 漏洞在传输途中拦截短信,无需接触你的手机。这项技术在地下已实现工具化。

钓鱼

最简单的攻击:一个模仿银行或服务的假网站或应用,诱骗你输入刚收到的 OTP。攻击者随后实时使用该 OTP 完成交易。

这些攻击并非理论上的。在菲律宾,中央银行(BSP)仅在 2024 年就记录了 7 万起金融欺诈投诉,最终导致监管规定在 2026 年中之前禁止高风险交易使用短信 OTP。

为什么应用内 OTP 更安全

应用内 OTP 是在认证器应用内部(如 Google Authenticator、Authy 或内置银行应用)生成并显示的。它从不经过蜂窝网络传输。这彻底消除了 SIM 卡劫持和 SS7 拦截。钓鱼也困难得多,因为代码与设备和应用会话绑定。

特性 短信 OTP 应用内 OTP
传输信道 蜂窝网络(不可控) 应用推送/本地生成(可控)
易受 SIM 卡劫持
易受 SS7 拦截
可钓鱼性 高(代码发送到手机,可转发) 低(代码与设备绑定)
用户体验 慢,需切换应用 快,一键确认
离线能力 是(TOTP)

何时使用哪种 OTP 方法

  • 短信 OTP 仍可用于低风险操作,如注册时验证手机号。但绝不应用于高风险交易(转账、密码修改、关键账户更新)。
  • 应用内 OTP(尤其是 TOTP——基于时间的一次性密码)是当前双因素认证(2FA)的最佳实践。它免费、开放标准(RFC 6238),且支持离线。
  • 基于推送的应用内 OTP(应用收到推送通知,你点击“批准”)提供更好的用户体验和安全性,因为它需要活跃的应用会话。

工作示例:实现应用内 TOTP

让我们用 Python 实现一个最小化的 TOTP。这是大多数应用内认证器的核心。

import hmac
import hashlib
import struct
import time

def hotp(secret: bytes, counter: int, digits: int = 6) -> str:
    """生成基于 HMAC 的一次性密码 (HOTP)。"""
    msg = struct.pack('>Q', counter)
    h = hmac.new(secret, msg, hashlib.sha1).digest()
    offset = h[-1] & 0x0f
    truncated = struct.unpack('>I', h[offset:offset+4])[0] & 0x7fffffff
    return str(truncated % (10 ** digits)).zfill(digits)

def totp(secret: bytes, digits: int = 6, interval: int = 30) -> str:
    """生成基于时间的一次性密码 (TOTP)。"""
    counter = int(time.time()) // interval
    return hotp(secret, counter, digits)

# 示例用法
secret = b'12345678901234567890'  # 生产环境中应安全生成
print(totp(secret))  # 例如 "287082"

关键点:

  • secret 必须安全生成(例如 os.urandom(20)),并通过二维码(使用 otpauth:// URI)分享给用户。
  • 计数器是当前 Unix 时间戳除以 30 秒。这给出了一个 30 秒的时间窗口。
  • 代码使用 HMAC-SHA1,这是 TOTP 的默认算法。也可以使用 SHA256 或 SHA512。

验证时,服务器计算相同的 TOTP 并与用户输入比较。常见做法是允许少量时间偏差(例如 ±1 个间隔)以应对时钟漂移。

OTP 实现中的常见陷阱

  • 硬编码密钥:绝不要将密钥嵌入客户端代码。使用二维码安全传输密钥。
  • 无速率限制:没有速率限制,攻击者可以在几分钟内暴力破解 6 位 OTP(100 万种可能)。在几次失败后实施指数退避或 CAPTCHA。
  • 有效期过长:10 分钟的 OTP 窗口太长了。TOTP 坚持 30 秒,电子邮件/短信使用 1-2 分钟。
  • 重复使用 OTP:每个 OTP 应一次性使用。一旦验证,即使仍在时间窗口内也不能再次接受。
  • 记录 OTP:绝不要记录 OTP 值。它们是敏感机密。
  • 忽略时钟偏差:TOTP 需要准确时间。服务器应允许少量偏差(例如 ±1 个间隔)。

常见问题

既然短信 OTP 不安全,为什么还在使用?

短信 OTP 易于实现,且可在没有应用的任何手机上工作。许多遗留系统依赖它。然而,监管机构正越来越多地禁止将其用于高风险交易。

HOTP 和 TOTP 有什么区别?

HOTP(基于 HMAC)使用一个每次使用递增的计数器。TOTP 使用时间作为计数器。TOTP 更常用于 2FA,因为服务器无需跟踪计数器。

应用内 OTP 能被钓鱼吗?

可以,但更困难。攻击者需要诱骗用户在假网站上实时输入代码。基于推送的批准(用户点击“批准”)更具抵抗力。

我应该在服务器还是客户端生成 OTP?

对于 TOTP,密钥在注册时共享,然后双方独立生成相同的代码。服务器必须验证,但生成可以在客户端进行。

对于高安全应用,最佳的 OTP 传递方法是什么?

应用内 TOTP 或基于推送的批准,结合设备上的生物识别验证(指纹/Face ID)。关键操作完全避免使用短信。