// OTP 安全

OTP 实现最佳实践:验证码、过期、限流与降级

一次性验证码看起来很简单——生成一个数字、发条短信、再校验它。但账号被盗的 bug 就藏在细节里。本指南涵盖让 OTP 流程真正安全又好用的那些决策:验证码熵值、存储、过期窗口、限流、重试与降级通道。

用真正的熵生成验证码

使用密码学安全的随机数生成器,绝不用可预测的种子或从时间戳推导的值。六位数通常是安全与易用的平衡;更短的验证码需要更严格的限流才能保持安全。

绝不发送两次相同的验证码,也绝不让验证码能从用户 id 或序列中猜出。短信 OTP 的全部安全,都系于验证码在其短暂生命周期内不可预测。

  • 使用 CSPRNG(如 secrets/crypto 库),而非通用 PRNG。
  • 6 位是标准;若用 4 位,就把限流与过期收紧到底。
  • 每次请求一个验证码;签发新码时使旧码失效。

安全存储验证码,绝不用明文

把 OTP 当作短命密码对待。存储验证码的哈希(连同标识与过期),而非原始值,这样即使数据库泄露也不会暴露有效验证码。用常数时间函数比较以避免时序攻击,并在验证码被使用或过期后立即删除记录。

  • 存储前对验证码做哈希;以常数时间比较。
  • 把每个验证码绑定到单一用户/会话与用途。
  • 在成功、过期或达到尝试上限后删除。

设置较短的过期窗口

验证码应存活数分钟,而非数小时。5–10 分钟的过期是常见区间:足够让延迟的短信仍能送达,又短到泄露的验证码通常已经作废。向用户显示剩余有效期,这样一条慢消息才不会被读作坏掉的流程。

!

把短过期与重发冷却搭配起来,否则消息延迟时用户会猛点重发,让你的短信成本翻倍。

在每个维度限流

限流是同时阻止暴力猜码与滥用你发送端点(那会花你的钱,还可能让你的发送方被标记)的手段。在发送与校验两侧,都按手机号、按 IP、按账号限流。

  • 校验尝试:对每个验证码限制尝试次数(如 5 次),随后使其失效并强制重发。
  • 发送请求:两次发送之间设冷却,并对每个号码设每日上限。
  • 全局护栏:按 IP 与按账号限流,钝化自动化滥用。
  • 在反复触及上限时加入指数退避或 CAPTCHA。

处理重试与降级通道

短信是尽力而为的,好的流程会假定有些消息不会送达。在冷却后提供重发,并在一两次短信失败后升级到降级通道——朗读验证码的语音呼叫、WhatsApp 或邮件——这正是托管 Verify API 所自动化的。

让失败状态有人情味:清晰的“没收到?”路径、更换号码的入口,以及反复失败后的客服联系方式。

  1. 1

    首次尝试:短信

    发送验证码,并启动带重发冷却的可见倒计时。

  2. 2

    按请求重发

    冷却后允许重发;签发新码时使旧码失效。

  3. 3

    升级通道

    反复短信失败后,提供语音或备用通道来交付同一次验证。

  4. 4

    提供退出

    让用户更正号码或联系客服,而不是走进死胡同。

在不削弱安全的前提下改善体验

一些小处理能提升完成率:使用 WebOTP API 与短信自动填充格式,让移动浏览器能读取验证码;保持消息措辞一致(有些平台会缓存受信任的发送方);消息里除了验证码与应用名,绝不放别的。避免在 OTP 消息中放链接——那会训练用户去点击,并触发运营商垃圾过滤。

  • 在移动端支持 WebOTP/一次性验证码自动填充。
  • 保持消息极简:应用名 + 验证码,无营销、无链接。
  • 本地化消息文本,但保持验证码格式稳定。

常见问题

OTP 应该有效多久?+

五到十分钟是常见窗口。它足够熬过延迟的短信,又短到泄露的验证码通常已经过期。请始终显示剩余时间,这样慢消息才不会看起来像坏掉的流程。

我应该允许多少次校验尝试?+

把每个验证码的尝试上限设在 5 次左右,随后使其失效并要求重新发送。再配合发送与校验两侧的按号码、按 IP、按账号限流,以阻止暴力猜测与发送端点滥用。

我该在 OTP 消息里放链接吗?+

不要。把 OTP 消息保持为只有你的应用名与验证码。链接会训练用户去点击、可能通过 referrer 泄露验证码,还常触发运营商垃圾过滤而损害送达。

短信 OTP 单独用够安全吗?+

短信 OTP 相较仅用密码是有意义的一步提升,但易受 SIM 换卡与拦截影响。对高价值账号,请提供通行密钥或 TOTP 等更强因素,并把短信当作降级——参见《OTP 替代方案》指南。

EdgeGigs

第一次就把 OTP 流程实现对。

在 EdgeGigs 上聘请经过审核的开发者,为你的网页或移动应用构建安全的验证码生成、过期、限流与降级。

在 EdgeGigs 上找开发者