邮件验证码的安全要点
邮件验证码实现中的常见安全漏洞:验证码强度、过期时间、速率限制、常数时间比较、用户枚举防护,以及日志规范。

邮件验证码的安全性不只是「代码能不能跑」,还在于整个流程有没有可以被攻击的地方。下面是几个常见的设计问题和对应的修法。
验证码强度
6 位纯数字有 1,000,000 种可能。加上速率限制之后,暴力穷举在实践中不可行。如果你的业务对安全要求更高(比如资金操作的二次验证),可以用 8 位数字或者 alphanumeric 验证码。
不要用时间戳、用户 ID 的哈希或可预测的序列生成验证码。用密码学安全的随机数生成器(Node.js 的 crypto.randomBytes,Python 的 secrets.token_digits)。
过期时间
验证码应该有明确的过期时间,通常 5 到 15 分钟。过期时间写进数据库或 Redis 的 TTL 里,不要只靠「发出去多久了」来判断。
用完即删:验证通过之后立即从存储里删掉这个验证码,防止同一个码被用两次。
速率限制
至少两层:
- 发码频率:同一个邮件地址,60 秒内只允许发一次新验证码。
- 验证尝试次数:同一个验证码,最多允许 3-5 次错误尝试,超过就作废,要求重新发码。
没有尝试次数限制的话,攻击者可以对一个有效期内的验证码逐一尝试所有 100 万种可能,而不触发任何警报。
常数时间比较
验证码匹配时要用常数时间比较,不要用普通的字符串 ==。字符串比较在遇到第一个不同字符时就返回 false,这个微小的时间差在大量请求下可以被用来推断验证码的前缀。
Node.js:crypto.timingSafeEqual(Buffer.from(input), Buffer.from(stored))
Python:hmac.compare_digest(input, stored)
防止用户枚举
验证码验证接口不应该区分「邮件地址不存在」和「验证码错误」,否则攻击者可以通过这个接口枚举哪些邮件地址在你的系统里注册了。统一返回「验证码不正确或已过期」。
传输安全
验证码通过 HTTPS 发出,确保不在网络传输层被截获。邮件本身没有强制加密,但这是邮件协议的固有局限,不是验证码设计的问题。
日志
不要把验证码明文记进日志。如果需要调试,记录「已发送 OTP 到 xxx@example.com」就够了,不要记录码本身。