传统字符验证码对用户不友好,且易被 OCR 破解。算术运算验证码要求用户计算简单加减乘除,既提高了机器识别成本,又降低了用户认知负担。本文提供基于 GD 库的完整实现,含前端表单、后端生成/校验、安全加固建议。
+----------+ 1. 请求验证码图片 +------------------+
| 浏览器 | -------------------------> | img.php (生成端) |
| | <------------------------- | - 随机生成算式 |
| | 2. 返回 PNG 图片流 | - GD 绘制干扰图 |
| | | - 答案存入 Session |
+----------+ +------------------+
| ^
| 3. 用户输入答案提交表单 | 4. 校验答案
v |
+----------+ +------------------+
| demo.php| -------------------------> | demo.php (校验端)|
| (表单页) | POST 答案 + Session ID | - 取 Session 答案 |
| | | - MD5 大小写不敏感比对 |
| | <------------------------- | - 返回通过/失败 |
+----------+ +------------------+
<?php
session_start();
// 处理登录提交
if (!empty($_POST['login'])) {
$input = strtoupper(trim($_POST['captcha']));
$expected = $_SESSION['captcha_answer'] ?? '';
if (md5($input) === $expected) {
echo '<div style="color:green">验证通过,欢迎登录!</div>';
} else {
echo '<div style="color:red">验证码错误,请重试。</div>';
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>算术验证码演示</title>
<style>
.captcha-img { vertical-align: middle; cursor: pointer; border: 1px solid #ddd; }
.captcha-img:hover { opacity: 0.8; }
input[name="captcha"] { width: 80px; text-align: center; }
</style>
</head>
<body>
<h3>登录验证</h3>
<form method="post">
<label>用户名: <input type="text" name="username" required></label><br><br>
<label>密码: <input type="password" name="password" required></label><br><br>
<label>验证码:
<input type="text" name="captcha" maxlength="10" required autocomplete="off">
<img class="captcha-img" src="img.php?= time() ?>" id="captchaImg" alt="点击刷新" title="点击刷新">
</label><br><br>
<button type="submit" name="login">登录</button>
</form>
<script>
document.getElementById('captchaImg').onclick = function() {
this.src = 'img.php?' + Date.now(); // 防缓存刷新
};
</script>
</body>
</html>
<?php
session_start();
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
// 配置参数
$width = isset($_GET['w']) ? max(80, min(300, (int)$_GET['w'])) : 120;
$height = isset($_GET['h']) ? max(30, min(100, (int)$_GET['h'])) : 40;
$fontSize = max(14, min(28, (int)($height * 0.6)));
// 1. 生成随机算式 (仅加减法,避免负数/除不尽)
function generateExpression(): array {
$a = random_int(1, 20);
$b = random_int(1, 20);
$op = random_int(0, 1) ? '+' : '-';
if ($op === '-' && $a < $b) [$a, $b] = [$b, $a]; // 保证非负
$expr = "$a $op $b";
$answer = $op === '+' ? $a + $b : $a - $b;
return ['expr' => $expr, 'answer' => (string)$answer];
}
[$expr, $answer] = generateExpression();
// 2. 存入 Session (MD5 大写,校验时大小写不敏感)
$_SESSION['captcha_answer'] = md5(strtoupper($answer));
// 3. GD 绘制
$img = imagecreatetruecolor($width, $height);
// 颜色分配
$bgColor = imagecolorallocate($img, 245, 248, 252); // 浅蓝灰背景
$textColor = imagecolorallocate($img, 30, 60, 120); // 深蓝文字
$noiseColor = imagecolorallocate($img, 180, 200, 220); // 干扰线颜色
$pixelColor = imagecolorallocate($img, 150, 170, 190); // 噪点颜色
// 填充背景
imagefill($img, 0, 0, $bgColor);
// 干扰元素:噪点
for ($i = 0; $i < $width * $height / 15; $i++) {
imagesetpixel($img, random_int(0, $width-1), random_int(0, $height-1), $pixelColor);
}
// 干扰元素:波浪线
for ($i = 0; $i < 4; $i++) {
$y = random_int($height/4, 3*$height/4);
$amp = random_int(3, 8);
for ($x = 0; $x < $width; $x++) {
$py = $y + (int)($amp * sin($x / 10));
if ($py >= 0 && $py < $height) imagesetpixel($img, $x, $py, $noiseColor);
}
}
// 干扰元素:随机直线
for ($i = 0; $i < 3; $i++) {
imageline($img, random_int(0, $width), random_int(0, $height), random_int(0, $width), random_int(0, $height), $noiseColor);
}
// 绘制算式文本 (居中)
$fontFile = __DIR__ . '/fonts/DejaVuSans-Bold.ttf'; // 需部署 TTF 字体
$box = imagettfbbox($fontSize, 0, $fontFile, $expr);
$textWidth = $box[2] - $box[0];
$textHeight = $box[1] - $box[7];
$x = ($width - $textWidth) / 2;
$y = ($height + $textHeight) / 2;
imagettftext($img, $fontSize, 0, $x, $y, $textColor, $fontFile, $expr);
// 边框
imagerectangle($img, 0, 0, $width-1, $height-1, imagecolorallocate($img, 100, 130, 160));
// 输出
imagepng($img);
imagedestroy($img);
$_SESSION,不暴露于 Cookie/URL/隐藏域。md5(strtoupper($input)) 兼容用户大小写习惯。unset($_SESSION['captcha_answer']),一次性失效。random_int() (CSPRNG) 而非 rand()/mt_rand()。Cache-Control: no-store + URL 时间戳防浏览器缓存。<button onclick="speechSynthesis.speak(new SpeechSynthesisUtterance('= $expr ?>'))">🔊</button>alt 属性提示「点击刷新验证码」,而非泄露答案。| 方案 | 安全性 | 用户体验 | 实现成本 | 适用场景 |
|---|---|---|---|---|
| 算术验证码 (本文) | 中 (可被专用 AI 破解) | 好 (无障碍友好) | 低 (纯 PHP+GD) | 中低风险表单、内部系统、学习演示 |
| Google reCAPTCHA v3 | 极高 (行为分析) | 极佳 (无感) | 低 (前端 JS + 后端验证) | 公开注册/登录/高价值表单 |
| hCaptcha | 高 (图像分类) | 中 (需点选) | 低 | 隐私敏感、中国大陆可用 |
| 滑块/拼图验证码 | 中高 (轨迹分析) | 中 (移动端友好) | 中 (需前端 SDK) | App/H5 原生应用 |
算术验证码是「安全够用、体验友好、零依赖、易部署」的经典方案。核心要点:CSPRNG 随机性、Session 绑定一次性校验、GD 干扰绘制、无障碍增强。对于面向公网的高价值业务,强烈建议接入 reCAPTCHA v3/hCaptcha 等成熟风控服务,将验证码作为纵深防御的一环而非单一依赖。