Search by

migears / captcha

samxxu

A lightweight captcha generator for PHP 8.1+ with GD extension

2.0.0 2026-10-01 13:22 UTC

This package is auto-updated.

Last update: 2026-10-01 14:01:16 UTC


README

Version

A lightweight captcha generation library for PHP 8.1+, requiring the GD extension to render images.

Background: miGears is the open-source successor of TinyGears, a self-developed PHP framework. It was renamed and open-sourced recently because the name TinyGears is already taken in the open-source community.

Features

  • Generates random captcha strings (numbers + letters, configurable length and character set)
  • Generates captcha images (configurable width/height, font, interference lines, noise dots)
  • Math captcha mode (math: true) — an arithmetic puzzle instead of random characters
  • Preset difficulty levels (easy / medium / hard) tune noise, lines and distortion
  • Supports custom TTF fonts
  • Supports three output formats: PNG, JPEG, GIF
  • CaptchaResult is a readonly value object
  • Multibyte-aware character sets (e.g. CJK)
  • CaptchaVerifier provides timing-safe, case-optional validation
  • Requires the GD extension (images are rendered with GD)
  • Minimalist API, outputs nothing, only returns data

Boundaries

In scope

  • Generating a random captcha code and rendering it to an image with GD (Captcha::generate()): configurable length, size, custom TTF font, noise, interference lines and character set; PNG / JPEG / GIF output.
  • Math mode (math: true) and the preset difficulty levels (Difficulty::Easy / Medium / Hard).
  • CaptchaResult, a readonly value object carrying imageData, code, mimeType and toDataUri().
  • CaptchaVerifier::verify(): a timing-safe (hash_equals), optionally case-insensitive comparison helper.

Not in scope (by design)

  • Storing the generated code (session, cache, ...) — the library does not persist it; caching belongs to migears/cache.
  • Expiry and one-time consumption of a code — the library does not track when a code was issued or whether it was already used.
  • Sending the HTTP response — the library outputs nothing and only returns data; emitting headers/body belongs to the caller or the framework.

Installation

composer require migears/captcha

Requires PHP 8.1 or higher and the GD extension.

Quick Start

use MiGears\Captcha\Captcha;
use MiGears\Captcha\ImageFormat;

$captcha = new Captcha(
    length: 4,
    width: 120,
    height: 40,
    format: ImageFormat::Png,
);

$result = $captcha->generate();

// Captcha text
echo $result->code;       // e.g.: a3fK

// Image binary data
echo $result->imageData;

// MIME type
echo $result->mimeType;   // image/png

// Data URI (convenient for embedding in HTML)
echo $result->toDataUri();// data:image/png;base64,...

Math Mode

Pass math: true to render an arithmetic puzzle instead of random characters. The code is the arithmetic result, which is what you store and verify:

use MiGears\Captcha\CaptchaVerifier;

$captcha = new Captcha(math: true);

$result = $captcha->generate();

// The image shows e.g. "7 + 3 = ?"
echo $result->code;   // "10" — the answer

// Verifying the user's typed answer:
$verifier = new CaptchaVerifier();
$ok = $verifier->verify($input, $result->code);

Uses +, -, * with operands 1–9; subtraction is always positive. When width/height are omitted, math mode defaults to 170×50 (wider to fit the extra characters).

length and chars are ignored in math mode: the puzzle supplies its own characters, so neither parameter is used or validated — new Captcha(math: true, length: 0, chars: '') is accepted.

Difficulty

Pick a preset that tunes noise, interference lines and character distortion (rotation + vertical drift):

use MiGears\Captcha\Difficulty;

$captcha = new Captcha(difficulty: Difficulty::Hard);
Level noiseLevel lineCount rotation drift
Easy 20 1 ±12° 3px
Medium 50 3 ±22° 6px
Hard 80 6 ±35° 10px

Explicit noiseLevel / lineCount always override the preset. When difficulty is omitted, the package keeps its original defaults (50 noise, 3 lines, no drift), so existing behavior is unchanged. Difficulty::Medium is a good middle ground when you want moderate distortion.

Configuration Options

Parameter Type Default Description
length int 4 Captcha character length (ignored in math mode)
width ?int 120 (170 in math mode) Image width in pixels
height ?int 40 (50 in math mode) Image height in pixels
font ?string Built-in font Custom TTF font file path
format ImageFormat ImageFormat::Png Output image format
noiseLevel ?int preset-dependent Number of noise dots
lineCount ?int preset-dependent Number of interference lines
chars string See below Captcha character set (ignored in math mode)
math bool false Render an arithmetic puzzle instead of random characters
difficulty ?Difficulty null Preset that tunes noise, lines and distortion

Default character set (easily confused characters 0/O/1/l/I removed):

abCDefGhiJkLmNPQrstUVWXyz23456789

Captcha Storage

This library does not handle captcha storage (Session / cache, etc.), it is managed by the user:

// Generate captcha
$result = $captcha->generate();

// Store the code (example: using Session)
$_SESSION['captcha_code'] = $result->code;

// Output the image
header('Content-Type: ' . $result->mimeType);
echo $result->imageData;

Verification

Compare a user-submitted answer against the stored code with CaptchaVerifier. It wraps timing-safe comparison and optional case folding, so callers don't have to implement these details themselves. When mbstring is available, folding is Unicode-aware (e.g. É -> é); otherwise it falls back to ASCII-only folding.

use MiGears\Captcha\CaptchaVerifier;

$verifier = new CaptchaVerifier();

// Case-insensitive by default
$verifier->verify('a3fk', 'a3fK'); // true

// Case-sensitive
$verifier->verify('a3fk', 'a3fK', caseInsensitive: false); // false

// Empty inputs always fail, no exception is thrown
$verifier->verify('', 'a3fK'); // false

Like storage, expiry and one-time consumption are left to the caller. The library does not track when a code was issued or whether it was already used:

$_SESSION['captcha_expires_at'] = time() + 300; // 5 minutes
$_SESSION['captcha_used'] = false;

// On submission:
if (!$_SESSION['captcha_used'] && time() < $_SESSION['captcha_expires_at']) {
    $ok = $verifier->verify($input, $_SESSION['captcha_code']);
    if ($ok) {
        $_SESSION['captcha_used'] = true; // consume
    }
}

Composite Example

Combining everything: a high-strength math captcha rendered to PNG, answered once within a 5-minute window, verified with timing-safe comparison:

use MiGears\Captcha\Captcha;
use MiGears\Captcha\Difficulty;
use MiGears\Captcha\CaptchaVerifier;

// Build a high-strength math captcha
$captcha = new Captcha(
    math: true,
    difficulty: Difficulty::Hard,
    format: \MiGears\Captcha\ImageFormat::Png,
);

$result = $captcha->generate();

// Store the answer (the math result) and an expiry
$_SESSION['captcha_code'] = $result->code;
$_SESSION['captcha_expires_at'] = time() + 300; // 5 minutes

// Serve the image
header('Content-Type: ' . $result->mimeType);
echo $result->imageData;

// ... later, validate the user's answer exactly once:
$verifier = new CaptchaVerifier();
$ok = time() < $_SESSION['captcha_expires_at']
    && $verifier->verify($_POST['captcha'] ?? '', $_SESSION['captcha_code']);

// Consume regardless of the outcome so a code can't be reused
unset($_SESSION['captcha_code'], $_SESSION['captcha_expires_at']);

Multibyte Note

The character set is multibyte-aware: a code generated from the default ASCII set or a UTF-8 set (e.g. Chinese) is always valid output. However, images are rendered with the bundled assets/captcha.ttf, which only contains Latin glyphs. To render non-Latin codes (CJK, Cyrillic, etc.), pass a font that covers those characters via the font option.

Exceptions

On failure, throws MiGears\Captcha\Exception\CaptchaException:

use MiGears\Captcha\Exception\CaptchaException;

try {
    $captcha = new Captcha(font: '/path/to/font.ttf');
    $result = $captcha->generate();
} catch (CaptchaException $e) {
    // handle the error
}

Testing

composer install
vendor/bin/phpunit

License

MIT

migears/captcha

Version

轻量级验证码生成库,PHP 8.1+,需要 GD 扩展来渲染图片。

特性

  • 生成随机验证码字符串(数字+字母,可配置长度和字符集)
  • 生成验证码图片(可配置宽高、字体、干扰线、噪点)
  • 数学算式验证码模式(math: true)——用算术题替代随机字符
  • 预设难度档位(easy / medium / hard)统一调整噪点、干扰线与扭曲
  • 支持自定义 TTF 字体
  • 支持 PNG、JPEG、GIF 三种输出格式
  • CaptchaResult 为 readonly 值对象
  • 字符集支持多字节(如中文)
  • CaptchaVerifier 提供常时安全、可选忽略大小的校验
  • 需要 GD 扩展(图片由 GD 渲染)
  • 极简 API,不输出任何内容,仅返回数据

边界

范围内

  • 用 GD 生成随机验证码并渲染成图片(Captcha::generate()):可配置长度、尺寸、自定义 TTF 字体、噪点、干扰线与字符集;输出 PNG / JPEG / GIF。
  • 数学算式模式(math: true)与预设难度档位(Difficulty::Easy / Medium / Hard)。
  • CaptchaResult:一个 readonly 值对象,携带 imageData、code、mimeType 与 toDataUri()。
  • CaptchaVerifier::verify():常时安全(hash_equals)、可选忽略大小写的比较辅助方法。

范围外(刻意不做)

  • 存储生成的 code(session、缓存等)—— 本库不做持久化;缓存由 migears/cache 负责。
  • code 的过期与一次性消费 —— 本库不追踪签发时间,也不记录是否已被用过。
  • 发送 HTTP 响应 —— 本库不输出任何内容,仅返回数据;响应头/响应体的发送属于调用方或框架。

安装

composer require migears/captcha

需要 PHP 8.1 及以上并安装 GD 扩展。

快速开始

use MiGears\Captcha\Captcha;
use MiGears\Captcha\ImageFormat;

$captcha = new Captcha(
    length: 4,
    width: 120,
    height: 40,
    format: ImageFormat::Png,
);

$result = $captcha->generate();

// 验证码文本
echo $result->code;       // 例如: a3fK

// 图片二进制数据
echo $result->imageData;

// MIME 类型
echo $result->mimeType;   // image/png

// Data URI(方便嵌入 HTML)
echo $result->toDataUri();// data:image/png;base64,...

数学算式模式

传入 math: true 渲染一道算术题,而非随机字符。code 即为算术结果, 用于存储和校验:

use MiGears\Captcha\CaptchaVerifier;

$captcha = new Captcha(math: true);

$result = $captcha->generate();

// 图片显示例如 "7 + 3 = ?"
echo $result->code;   // "10" —— 答案

// 校验用户输入的答案:
$verifier = new CaptchaVerifier();
$ok = $verifier->verify($input, $result->code);

使用 +、-、*,操作数为 1–9,减法恒为正。当未显式指定 width/height 时,数学模式下默认 170×50(更宽以容纳额外字符)。

数学模式下 length 与 chars 被忽略:题目自带字符,二者既不参与生成也不做 校验 —— new Captcha(math: true, length: 0, chars: '') 会被接受。

难度档位

通过预设档位统一调整噪点、干扰线与字符扭曲(旋转角 + 垂直漂移):

use MiGears\Captcha\Difficulty;

$captcha = new Captcha(difficulty: Difficulty::Hard);
档位 noiseLevel lineCount 旋转角 漂移
Easy 20 1 ±12° 3px
Medium 50 3 ±22° 6px
Hard 80 6 ±35° 10px

显式传入的 noiseLevel / lineCount 始终优先于预设。当不指定 difficulty 时,包保持原有默认(50 噪点、3 干扰线、无漂移),现有行为不变。想获得适度 扭曲时,Difficulty::Medium 是不错的中间档。

配置选项

参数 类型 默认值 说明
length int 4 验证码字符长度(数学模式忽略)
width ?int 120(数学模式 170) 图片宽度(像素)
height ?int 40(数学模式 50) 图片高度(像素)
font ?string 内置字体 自定义 TTF 字体文件路径
format ImageFormat ImageFormat::Png 输出图片格式
noiseLevel ?int 随档位而定 噪点数量
lineCount ?int 随档位而定 干扰线数量
chars string 见下 验证码字符集(数学模式忽略)
math bool false 用算术题替代随机字符
difficulty ?Difficulty null 预设档位,统一调整噪点/干扰线/扭曲

默认字符集(已去除易混淆字符 0/O/1/l/I):

abCDefGhiJkLmNPQrstUVWXyz23456789

验证码存储

本库不负责验证码的存储(Session / 缓存等),由使用者自行管理:

// 生成验证码
$result = $captcha->generate();

// 存储 code(示例:使用 Session)
$_SESSION['captcha_code'] = $result->code;

// 输出图片
header('Content-Type: ' . $result->mimeType);
echo $result->imageData;

验证码校验

使用 CaptchaVerifier 将用户提交的答案与已存储的 code 进行比较。它封装了 常时比较 和可选的大小写折叠,调用方无需 自己实现这些细节。装有 mbstring 时折叠为 Unicode 感知(如 É → é), 否则回退到仅 ASCII 折叠。

use MiGears\Captcha\CaptchaVerifier;

$verifier = new CaptchaVerifier();

// 默认忽略大小写
$verifier->verify('a3fk', 'a3fK'); // true

// 区分大小写
$verifier->verify('a3fk', 'a3fK', caseInsensitive: false); // false

// 空输入恒为 false,不抛异常
$verifier->verify('', 'a3fK'); // false

与存储一样,过期与一次性消费由调用方负责。本库不追踪 code 的签发时间, 也不记录是否已被用过:

$_SESSION['captcha_expires_at'] = time() + 300; // 5 分钟过期
$_SESSION['captcha_used'] = false;

// 用户提交时:
if (!$_SESSION['captcha_used'] && time() < $_SESSION['captcha_expires_at']) {
    $ok = $verifier->verify($input, $_SESSION['captcha_code']);
    if ($ok) {
        $_SESSION['captcha_used'] = true; // 消费
    }
}

完整示例

组合所有能力:生成一张高强度数学验证码 PNG,限定 5 分钟内一次性作答, 并用常时比较校验:

use MiGears\Captcha\Captcha;
use MiGears\Captcha\Difficulty;
use MiGears\Captcha\ImageFormat;
use MiGears\Captcha\CaptchaVerifier;

// 构建高强度数学验证码
$captcha = new Captcha(
    math: true,
    difficulty: Difficulty::Hard,
    format: ImageFormat::Png,
);

$result = $captcha->generate();

// 存储答案(算术结果)与过期时间
$_SESSION['captcha_code'] = $result->code;
$_SESSION['captcha_expires_at'] = time() + 300; // 5 分钟

// 输出图片
header('Content-Type: ' . $result->mimeType);
echo $result->imageData;

// ……稍后,仅允许一次性校验用户的答案:
$verifier = new CaptchaVerifier();
$ok = time() < $_SESSION['captcha_expires_at']
    && $verifier->verify($_POST['captcha'] ?? '', $_SESSION['captcha_code']);

// 无论对错都消费,避免验证码被复用
unset($_SESSION['captcha_code'], $_SESSION['captcha_expires_at']);

多字节说明

字符集支持多字节:无论用默认 ASCII 字符集还是 UTF-8 字符集(如中文),生成 的 code 都是合法的。但图片使用内置 assets/captcha.ttf 渲染,该字体仅含 拉丁字形。 若要渲染非拉丁字符(中文、西里尔等),请通过 font 选项传入 支持这些字符的字体。

异常

失败时抛出 MiGears\Captcha\Exception\CaptchaException:

use MiGears\Captcha\Exception\CaptchaException;

try {
    $captcha = new Captcha(font: '/path/to/font.ttf');
    $result = $captcha->generate();
} catch (CaptchaException $e) {
    // 处理错误
}

测试

composer install
vendor/bin/phpunit

License

MIT