PHP验证码安全需确保GD扩展启用、字体路径绝对且可读、干扰设计反OCR;GD未启用则imagecreatetruecolor()报错,字体路径错误导致imagettftext()失败,规律性干扰反而提升OCR识别率。

PHP生成验证码不是“画张图存个Session”就完事——关键在于干扰是否有效、Session是否被劫持、字体路径是否真实可读,三者任一出错,验证码就形同虚设。
GD扩展没启用,imagecreatetruecolor() 直接报错
很多环境默认禁用GD,尤其Docker容器或云函数里。不检查就写代码,运行时直接500,连错误提示都看不到(因为header已发送或错误被静默)。
- 先执行
extension_loaded('gd'),返回false就别往下走了 - CLI下验证:运行
php -m | grep gd,没输出就得装 —— Ubuntu用apt install php-gd,CentOS用yum install php-gd - Apache/Nginx重启后,还要确认
phpinfo()页面里真有GD模块,且支持PNG/JPEG(有些精简版只开GIF)
imagettftext() 报 “Could not find/open font” 怎么办
这个错99%是路径问题,不是字体坏了。GD不会自动找系统字体,必须给绝对路径,相对路径在Web请求中极易失效。
- 别用
./fonts/arial.ttf或fonts/arial.ttf—— 改成__DIR__ . '/fonts/arial.ttf'或完整路径如/var/www/myapp/fonts/arial.ttf - 确认文件存在且PHP进程有读权限:
is_readable($font_path)必须返回true - Linux下注意大小写:
Arial.ttf≠arial.ttf;Windows下虽不敏感,但部署到Linux就炸 - 没合适字体?用系统自带的:Ubuntu可试
/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
验证码被OCR识别率高,不是加得少,而是加得太“规律”
一堆等距横线、固定步长噪点,反而成了机器学习模型的锚点特征。人眼觉得乱,算法觉得整齐。
立即学习“PHP免费学习笔记(深入)”;
- 干扰线别用
for ($i=0; $i —— 改成随机起点+随机长度:<code>imageline($img, rand(0, $width), rand(0, $height/2), rand(0, $width), rand($height/2, $height), $color) - 噪点密度控制在 0.8% 左右:比如 200×60 画布共12000像素,循环30–100次足够,再多字就糊了
- 文字别全用同一字号/角度 —— 每个字符单独调用
imagettftext(),$angle = mt_rand(-8, 8),$x += mt_rand(-2, 2) - 避免纯黑字+白底:文字色用
rand(30, 60)灰度,背景用rand(235, 245)浅灰,对比度够但不刺眼
Session存储和校验时最容易忽略的三个细节
验证码能生成,不代表能安全验证。Session管理出问题,等于把钥匙挂门把手上。
- 生成脚本和校验脚本都必须调用
session_start(),且不能有任何输出(包括BOM、空格、echo)在它之前 - 校验后立刻
unset($_SESSION['captcha_code'])—— 不清空,同一码可反复提交;不清早,别人截包重放也能过 - Session有效期要主动设:
ini_set('session.gc_maxlifetime', 300)在生成脚本开头加,否则依赖php.ini默认值(常为1440秒),太长易被暴力穷举 - 别用
$_SESSION['code']这种通用键名 —— 攻击者可能伪造其他流程的Session覆盖它,用$_SESSION['login_captcha_'.md5(__FILE__)]更稳妥
真正难的不是画出一张图,而是让这张图在不增加用户输入负担的前提下,挡住自动化工具又不被人工误判。每加一条干扰线、每改一个颜色值、每调一次 mt_rand(),都要问一句:它让机器更难了,还是只是让调试更累了。



















