現代驗證碼需同時滿足「人類易讀」與「機器難識」兩大目標。本文提供的高安全驗證碼類別採用 TrueType 字體渲染,結合字符隨機旋轉、基線抖動、多層干擾元素 (噪點、干擾字符)、隨機背景色等技術,大幅提升 OCR 識別難度,適用於登入、註冊、支付確認等高安全場景。
<?php
/**
* 高安全驗證碼生成類別
* 需求: PHP GD2 擴充套件、FreeType 支援、.ttf 字體檔
* 特色: TrueType 字體、字符隨機旋轉/位移、多層干擾、Session 儲存
*/
class HighSecurityCaptcha {
// --- 可配置屬性 ---
private $fontFile; // 字體檔絕對路徑
private $imgWidth = 130; // 圖片寬度
private $imgHeight = 40; // 圖片高度
private $fontSize = 18; // 字體大小
private $angleRange = 25; // 旋轉角度範圍 (±度)
private $codeLength = 4; // 驗證碼長度
private $charset = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // 字符集 (排除易混淆)
private $sessionKey = 'hs_captcha_code'; // Session 儲存鍵名
private $noiseChars = 20; // 干擾字符數量
private $noisePoints = 100; // 噪點數量
private $bgColorRange = [200, 255]; // 背景色範圍 (淺色系)
private $textColorRange = [0, 120]; // 文字色範圍 (深色系)
// --- 建構子 ---
public function __construct(array $config = []) {
foreach ($config as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}
// 預設字體路徑檢查
if (empty($this->fontFile)) {
// 嘗試常見路徑
$defaultFonts = [
__DIR__ . '/fonts/DejaVuSans.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', // Linux
'C:/Windows/Fonts/arial.ttf', // Windows
];
foreach ($defaultFonts as $font) {
if (is_file($font)) {
$this->fontFile = $font;
break;
}
}
}
if (empty($this->fontFile) || !is_file($this->fontFile)) {
throw new Exception('找不到 TrueType 字體檔,請設定 fontFile 參數');
}
}
// --- 核心生成方法 ---
public function generate(): string {
// 1. 啟動 Session (若未啟動)
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// 2. 發送防快取標頭
$this->sendHeaders();
// 3. 生成隨機驗證碼
$code = $this->generateCode();
$_SESSION[$this->sessionKey] = strtolower($code); // 統一小寫驗證
// 4. 建立真彩色畫布
$image = imagecreatetruecolor($this->imgWidth, $this->imgHeight);
// 5. 隨機背景色 (淺色系)
$bgColor = $this->allocateRandomColor($image, $this->bgColorRange);
imagefilledrectangle($image, 0, 0, $this->imgWidth - 1, $this->imgHeight - 1, $bgColor);
// 6. 繪製干擾字符 (背景層)
$this->drawNoiseChars($image);
// 7. 繪製噪點
$this->drawNoisePoints($image);
// 8. 繪製驗證碼字符 (前景層)
$this->drawCodeChars($image, $code);
// 9. 輸出並銷毀
imagepng($image);
imagedestroy($image);
return $code;
}
// --- 驗證方法 ---
public static function verify(string $input, string $sessionKey = 'hs_captcha_code'): bool {
if (session_status() === PHP_SESSION_NONE) session_start();
$stored = $_SESSION[$sessionKey] ?? '';
$valid = (strtolower(trim($input)) === $stored) && !empty($input);
if ($valid) {
unset($_SESSION[$sessionKey]); // 一次性使用
}
return $valid;
}
// --- 私有輔助方法 ---
private function sendHeaders(): void {
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
}
private function generateCode(): string {
$code = '';
$len = strlen($this->charset) - 1;
for ($i = 0; $i < $this->codeLength; $i++) {
$code .= $this->charset[random_int(0, $len)];
}
return $code;
}
private function allocateRandomColor($image, array $range): int {
return imagecolorallocate(
$image,
random_int($range[0], $range[1]),
random_int($range[0], $range[1]),
random_int($range[0], $range[1])
);
}
private function drawNoiseChars($image): void {
for ($i = 0; $i < $this->noiseChars; $i++) {
$color = $this->allocateRandomColor($image, [150, 220]); // 中間色調
$char = $this->charset[random_int(0, strlen($this->charset) - 1)];
$x = random_int(-10, $this->imgWidth);
$y = random_int(-10, $this->imgHeight);
$size = random_int(10, 16);
$angle = random_int(-30, 30);
imagettftext($image, $size, $angle, $x, $y, $color, $this->fontFile, $char);
}
}
private function drawNoisePoints($image): void {
for ($i = 0; $i < $this->noisePoints; $i++) {
$color = $this->allocateRandomColor($image, [100, 200]);
imagesetpixel($image, random_int(0, $this->imgWidth), random_int(0, $this->imgHeight), $color);
}
}
private function drawCodeChars($image, string $code): void {
$charWidth = $this->imgWidth / $this->codeLength;
$baseLine = $this->imgHeight * 0.7; // 基線位置 (約 70% 高度)
for ($i = 0; $i < $this->codeLength; $i++) {
$char = $code[$i];
// 隨機顏色 (深色系)
$color = $this->allocateRandomColor($image, $this->textColorRange);
// 隨機旋轉角度
$angle = random_int(-$this->angleRange, $this->angleRange);
// X 座標: 字符區塊內隨機 + 微調
$x = $i * $charWidth + random_int(5, 15);
// Y 座標: 基線上下隨機抖動
$y = $baseLine + random_int(-5, 5);
imagettftext($image, $this->fontSize, $angle, $x, $y, $color, $this->fontFile, $char);
}
}
}
// --- 使用範例 (captcha.php) ---
/*
require_once 'HighSecurityCaptcha.php';
try {
$captcha = new HighSecurityCaptcha([
'fontFile' => __DIR__ . '/fonts/DejaVuSans.ttf', // 必須指定有效字體路徑
'imgWidth' => 140,
'imgHeight' => 45,
'codeLength' => 5,
'angleRange' => 30,
]);
$captcha->generate();
} catch (Exception $e) {
// 生產環境記錄錯誤日誌,回傳預設圖片或錯誤訊息
error_log('Captcha Error: ' . $e->getMessage());
header('Content-Type: image/png');
// 可輸出一張預設錯誤圖片
}
*/
?>
<form action="verify.php" method="post">
<div class="captcha-wrapper">
<img id="captchaImg" src="captcha.php" alt="高安全驗證碼" title="點擊更換" style="cursor:pointer; vertical-align:middle;">
<button type="button" id="refreshBtn" aria-label="更換驗證碼" style="vertical-align:middle;">🔄</button>
</div>
<input type="text" name="captcha" id="captchaInput" placeholder="請輸入驗證碼" required autocomplete="off">
<button type="submit">提交</button>
</form>
<script>
document.getElementById('captchaImg').addEventListener('click', refreshCaptcha);
document.getElementById('refreshBtn').addEventListener('click', refreshCaptcha);
function refreshCaptcha() {
const img = document.getElementById('captchaImg');
const input = document.getElementById('captchaInput');
img.src = 'captcha.php?' + Date.now(); // 強制重新請求
input.value = '';
input.focus();
}
</script>
<?php
require_once 'HighSecurityCaptcha.php';
session_start();
$input = $_POST['captcha'] ?? '';
if (HighSecurityCaptcha::verify($input)) {
echo '✅ 驗證通過,執行業務邏輯...';
// 登入、註冊、提交訂單等
} else {
echo '❌ 驗證碼錯誤或已過期,請重試';
}
?>
| 項目 | 說明 | 驗證方式 |
|---|---|---|
| GD2 + FreeType | PHP 編譯需支援 --with-freetype | php -m | grep gd、gd_info()['FreeType Support'] |
| 字體檔權限 | .ttf 檔案需可被 PHP 進程讀取 (www-data 權限) | ls -l fonts/、is_readable() 測試 |
| Session 目錄可寫 | session.save_path 目錄需存在且可寫入 | session_start() 無警告 |
| 防快取標頭 | 確保瀏覽器不快取驗證碼圖片 | 開發者工具 Network 檢查 Response Headers |
| 錯誤處理 | 生產環境不可輸出 PHP 錯誤訊息到圖片流 | display_errors = Off、自訂錯誤處理器 |
imagecopyresampled 實作正弦波扭曲,進一步干擾字符分割。本文提供的 HighSecurityCaptcha 類別採用現代 PHP 物件導向設計,封裝了 TrueType 字體渲染、隨機旋轉/位移、多層干擾元素、防快取標頭、一次性驗證等高安全特性。開發者只需提供有效的 .ttf 字體路徑即可快速整合。記住:驗證碼安全性取決於「干擾複雜度」與「字符變化度」的平衡,建議定期更新字體、調整干擾參數,並配合前端無感驗證、後端風控系統構建纏深度防禦體系。