用真正的熵生成验证码
使用密码学安全的随机数生成器,绝不用可预测的种子或从时间戳推导的值。六位数通常是安全与易用的平衡;更短的验证码需要更严格的限流才能保持安全。
绝不发送两次相同的验证码,也绝不让验证码能从用户 id 或序列中猜出。短信 OTP 的全部安全,都系于验证码在其短暂生命周期内不可预测。
- 使用 CSPRNG(如 secrets/crypto 库),而非通用 PRNG。
- 6 位是标准;若用 4 位,就把限流与过期收紧到底。
- 每次请求一个验证码;签发新码时使旧码失效。
安全存储验证码,绝不用明文
把 OTP 当作短命密码对待。存储验证码的哈希(连同标识与过期),而非原始值,这样即使数据库泄露也不会暴露有效验证码。用常数时间函数比较以避免时序攻击,并在验证码被使用或过期后立即删除记录。
- 存储前对验证码做哈希;以常数时间比较。
- 把每个验证码绑定到单一用户/会话与用途。
- 在成功、过期或达到尝试上限后删除。
设置较短的过期窗口
验证码应存活数分钟,而非数小时。5–10 分钟的过期是常见区间:足够让延迟的短信仍能送达,又短到泄露的验证码通常已经作废。向用户显示剩余有效期,这样一条慢消息才不会被读作坏掉的流程。
把短过期与重发冷却搭配起来,否则消息延迟时用户会猛点重发,让你的短信成本翻倍。
在每个维度限流
限流是同时阻止暴力猜码与滥用你发送端点(那会花你的钱,还可能让你的发送方被标记)的手段。在发送与校验两侧,都按手机号、按 IP、按账号限流。
- 校验尝试:对每个验证码限制尝试次数(如 5 次),随后使其失效并强制重发。
- 发送请求:两次发送之间设冷却,并对每个号码设每日上限。
- 全局护栏:按 IP 与按账号限流,钝化自动化滥用。
- 在反复触及上限时加入指数退避或 CAPTCHA。
处理重试与降级通道
短信是尽力而为的,好的流程会假定有些消息不会送达。在冷却后提供重发,并在一两次短信失败后升级到降级通道——朗读验证码的语音呼叫、WhatsApp 或邮件——这正是托管 Verify API 所自动化的。
让失败状态有人情味:清晰的“没收到?”路径、更换号码的入口,以及反复失败后的客服联系方式。
- 1
首次尝试:短信
发送验证码,并启动带重发冷却的可见倒计时。
- 2
按请求重发
冷却后允许重发;签发新码时使旧码失效。
- 3
升级通道
反复短信失败后,提供语音或备用通道来交付同一次验证。
- 4
提供退出
让用户更正号码或联系客服,而不是走进死胡同。
在不削弱安全的前提下改善体验
一些小处理能提升完成率:使用 WebOTP API 与短信自动填充格式,让移动浏览器能读取验证码;保持消息措辞一致(有些平台会缓存受信任的发送方);消息里除了验证码与应用名,绝不放别的。避免在 OTP 消息中放链接——那会训练用户去点击,并触发运营商垃圾过滤。
- 在移动端支持 WebOTP/一次性验证码自动填充。
- 保持消息极简:应用名 + 验证码,无营销、无链接。
- 本地化消息文本,但保持验证码格式稳定。