傳統驗證碼全字符同色易被 OCR 識別。本文實現「多彩驗證碼」:隨機選取 2 個字符著色,用戶僅需輸入指定顏色字符,大幅提升機器識別難度。
<?php
header('Content-Type: image/png; charset=utf-8');
// 畫布尺寸
$width = 400;
$height = 60;
$im = imagecreatetruecolor($width, $height);
// 顏色分配
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 40, 40, 40);
$gray = imagecolorallocate($im, 150, 150, 150);
$red = imagecolorallocate($im, 220, 50, 50);
$blue = imagecolorallocate($im, 50, 100, 220);
$green = imagecolorallocate($im, 50, 180, 50);
$special_colors = [$red, $blue, $green];
$base_colors = [$black, $gray];
// 字符池與文本生成
$charset = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
$len = 6;
$text = '';
for ($i = 0; $i < $len; $i++) {
$text .= $charset[random_int(0, strlen($charset) - 1)];
}
// 隨機選取 1~2 個特殊位置
$special_count = random_int(1, 2);
$special_pos = [];
while (count($special_pos) < $special_count) {
$pos = random_int(0, $len - 1);
if (!in_array($pos, $special_pos, true)) {
$special_pos[] = $pos;
}
}
$special_color = $special_colors[array_rand($special_colors)];
// 字體設置(需確保字體文件存在)
$font = __DIR__ . '/fonts/NotoSansSC-Regular.otf'; // 建議使用開源中文字體
$font_size = 24;
// 繪製背景
imagefilledrectangle($im, 0, 0, $width - 1, $height - 1, $white);
// 可選:添加干擾線/點
for ($i = 0; $i < 8; $i++) {
$line_color = imagecolorallocatealpha($im, 200, 200, 200, 80);
imageline($im, random_int(0, $width), random_int(0, $height), random_int(0, $width), random_int(0, $height), $line_color);
}
for ($i = 0; $i < 100; $i++) {
$dot_color = imagecolorallocatealpha($im, 100, 100, 100, 60);
imagesetpixel($im, random_int(0, $width), random_int(0, $height), $dot_color);
}
// 逐字符繪製
$char_width = $width / $len;
for ($i = 0; $i < $len; $i++) {
$x = (int)($i * $char_width + $char_width * 0.15);
$y = (int)($height * 0.65);
$angle = random_int(-12, 12);
$color = in_array($i, $special_pos, true) ? $special_color : $base_colors[array_rand($base_colors)];
imagettftext($im, $font_size, $angle, $x, $y, $color, $font, $text[$i]);
}
// 輸出並銷毀
imagepng($im);
imagedestroy($im);
// 將驗證信息存入 Session 供驗證
session_start();
$_SESSION['captcha'] = [
'text' => $text,
'special_pos' => $special_pos,
'special_color' => '#' . sprintf('%02x%02x%02x',
($special_color >> 16) & 0xFF,
($special_color >> 8) & 0xFF,
$special_color & 0xFF
),
'expire' => time() + 300 // 5 分鐘有效
];
?>
<!-- HTML -->
<img id="captcha" src="captcha.php" alt="驗證碼" style="cursor:pointer" onclick="this.src='captcha.php?'+Date.now()">
<p>請輸入 <span id="color_hint" style="font-weight:bold"></span> 字符</p>
<input type="text" name="captcha_input" id="captcha_input" maxlength="2">
<script>
// 獲取顏色提示(需從後端 API 獲取或內聯輸出)
fetch('captcha_info.php')
.then(r => r.json())
.then(d => {
document.getElementById('color_hint').textContent = d.color_name;
document.getElementById('color_hint').style.color = d.color_hex;
});
// 提交時驗證
document.querySelector('form').addEventListener('submit', async (e) => {
const input = document.getElementById('captcha_input').value.toUpperCase();
const res = await fetch('verify_captcha.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({input})
});
const data = await res.json();
if (!data.ok) { alert(data.msg); e.preventDefault(); }
});
</script>
<?php // verify_captcha.php
session_start();
header('Content-Type: application/json');
$input = strtoupper(trim($_POST['input'] ?? ''));
$captcha = $_SESSION['captcha'] ?? null;
if (!$captcha || time() > $captcha['expire']) {
echo json_encode(['ok' => false, 'msg' => '驗證碼已過期']);
exit;
}
$text = $captcha['text'];
$special_pos = $captcha['special_pos'];
$expected = '';
foreach ($special_pos as $pos) {
$expected .= $text[$pos];
}
if ($input === $expected) {
unset($_SESSION['captcha']); // 一次性使用
echo json_encode(['ok' => true]);
} else {
echo json_encode(['ok' => false, 'msg' => '驗證碼錯誤']);
}
多彩驗證碼通過「人類易辨、機器難分」的顏色維度,在不犧牲用戶體驗前提下顯著提升防機器能力。