PHP验证码生成不能直接用rand()拼字符串,因其随机性不均匀、易产生连续重复字符(如"aaabbb"),且无法控制字符集,难以避开易混淆字符(如O/0、I/1)及危险特殊字符。

PHP验证码生成时为什么不能直接用 rand() 拼字符串
因为 rand() 生成的随机数不均匀,且容易产生连续重复字符(比如 "aaabbb"),更关键的是——它不控制字符集,你没法保证避开易混淆字符(如 O 和 0、I 和 1)或危险特殊字符(如 、<code>&、")。如果后续把验证码直接输出到 HTML 或存进数据库没转义,可能引发 XSS 或 SQL 注入。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用白名单数组定义安全字符集,显式排除
O、0、I、1、l、!、@、$、、<code>>等 - 用
str_shuffle()+substr()替代循环调用rand(),避免重复倾向 - 生成后立即用
htmlspecialchars()或htmlentities()处理再输出,不依赖前端过滤
如何用 array_diff() 动态过滤掉用户提交的验证码中的非法字符
别在验证阶段才检查“是否含特殊字符”——要先清洗。用户 POST 上来的验证码(比如 $_POST['captcha'])可能被手动篡改、粘贴带空格或全角字符,甚至插入零宽空格(\u200b)绕过前端 JS 校验。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
preg_replace('/[\x{200b}-\x{200d}\x{feff}]/u', '', $input)清除常见 Unicode 零宽字符 - 用
trim()去首尾空白,再用str_replace()批量替换全角空格、中文标点为英文等价字符(可选) - 构建合法字符白名单数组,用
str_split()拆输入字符串,再用array_diff()找出不在白名单里的字符:若结果非空,直接拒绝,不进入比对逻辑
imagefttext() 渲染验证码时,为什么字母 g、q、y 容易被识别成数字 9 或 2
不是字体问题,是抗锯齿和字号太小导致字形粘连变形。默认用 imagefttext() 渲染时,如果字体文件本身有连笔或下沉设计(比如某些免费 TTF),加上 PHP GD 库对 hinting 支持弱,g 的下挂部分会和下一行像素混叠,OCR 工具或人眼都易误判。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 禁用易混淆字母:从字符集中移除
g、q、y、z、s(尤其和5混淆) - 强制设置足够字号(至少
16px),并用imagettfbbox()测量实际宽度,避免字符挤压 - 加轻微旋转(
-5到+5度)和水平位移,但别超过2px,否则影响可读性
验证码 session 存储前要不要对值做 md5() 或 hash_equals() 校验
要,但不是为了“加密”,而是防时序攻击。直接用 == 或 === 比较用户输入和 session 中的明文验证码,PHP 字符串比较会在第一个字节不同时立刻返回 false,攻击者可通过响应时间差异暴力猜解(尤其 4 位验证码,最多 4×36 次请求就能穷举)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- session 中仍存明文(便于调试和日志),但比对时必须用
hash_equals(md5($expected), md5($input)) - 或者更简洁:用
hash_equals($expected, $input)——PHP 5.6+ 原生支持恒定时间比较,前提是两个参数都是字符串且长度一致;所以得先校验长度,长度不对直接拒绝 - 别用
password_hash():它带盐且不可逆,无法用于即时比对
== 直接比对。这些地方不出问题则已,一出就是批量绕过。



















