PHP＋GDで日本語CAPTCHAを作る：ひらがな＋ノイズ線でBot対策

## 背景
Altaryに海外botからと思われるアクセスが多数あり、対策として日本語入力を求めるCAPTCHAを作成することとした。

## やりたいこと
・PHP＋GD（GD Graphics Library）で入力させる文字列を含む画像を生成する。
・日本語（ひらがな）を入力させる。
・OCR対策にノイズ(ランダムな数本の直線)を加える。

## コード
```
<?php
session_start();

function displayRecaptcha() {
    //いるだけ文字列のパターンを。
    $charset = ['あ','い','う','え','お','か','き','く','け','こ'・・・・・・・・・・・];
    $text = '';
    for ($i = 0; $i < 5; $i++) {
        $text .= $charset[array_rand($charset)];
    }

    // セッションに格納
    $_SESSION['captcha_code'] = $text;

    // 画像生成
    $width = 180;
    $height = 60;
    $image = imagecreatetruecolor($width, $height);
    $bgColor = imagecolorallocate($image, 255, 255, 255);
    $textColor = imagecolorallocate($image, 20, 20, 20);
    $noiseColor = imagecolorallocate($image, 150, 150, 150);

    imagefilledrectangle($image, 0, 0, $width, $height, $bgColor);

    $fontPath = __DIR__ . '/font-sample-file.ttf'; 
    // 日本語表示可能なフォントを配置（例：Google FontsのNoto Sans JPなど）

    imagesetthickness($image, 3);

    // ノイズ線を追加
    for ($i = 0; $i < 5; $i++) {
        imageline(
            $image,
            rand(0, $width), rand(0, $height),
            rand(0, $width), rand(0, $height),
            $noiseColor
        );
    }

    // 文字描画
    for ($i = 0; $i < mb_strlen($text); $i++) {
        $char = mb_substr($text, $i, 1);
        $angle = rand(-25, 25);
        $x = 20 + $i * 30 + rand(-2, 2);
        $y = 40 + rand(-5, 5);

        imagettftext($image, 24, $angle, $x, $y, $textColor, $fontPath, $char);
    }

    ob_start();
    imagepng($image);
    $imageData = ob_get_clean();
    imagedestroy($image);

    return 'data:image/png;base64,' . base64_encode($imageData);
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>日本語CAPTCHAテスト</title>
</head>
<body>
    <h1>日本語CAPTCHA テスト</h1>

    <?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $userInput = $_POST['captcha_input'] ?? '';
        $correct = $_SESSION['captcha_code'] ?? '';

        if ($userInput === $correct) {
            echo '<p style="color: green;">✅ 正解です！</p>';
        } else {
            echo '<p style="color: red;">❌ 間違っています。</p>';
        }

        // 再生成（同じ文字列の再送信防止）
        echo '<hr>';
    }

    $captcha = displayRecaptcha();
    ?>

    <form method="POST">
        <p>
            <img src="<?= $captcha ?>" alt="CAPTCHA" />
        </p>
        <p>
            表示されているひらがなを入力してください：<br>
            <input type="text" name="captcha_input" placeholder="例：あかさたな" required>
        </p>
        <p>
            <button type="submit">送信</button>
        </p>
    </form>
</body>
</html>
```

## 結果
こんな感じのフォームができます。
![スクリーンショット 2025-04-20 16.26.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4026790/f010beab-e6e6-468c-a777-77a08c8490bf.png)

※あくまで表示例です。実際にはフォームの形式やCSS等の調整が必要です。

## OCRで読み取られないか？
↑の画像をChatGPTに投げて、読み取られるか試してみました。

結果は、「こさむけ」。

実際の表示は「ここおくし」なので、現時点（2025年4月20日）のChatGPT4oレベルのOCRであれば対策できている模様です。

※ただし、AI OCR技術は日々進化しており、今回の結果が将来的にも同様に有効とは限りません。定期的に文字数やノイズの量、角度、フォントを変えるなどの対策をおすすめします。
また、より精度を高めたいのであればreCAPTCHA（google公式提供のCAPTCHA機能）の導入をお勧めします。

## まとめ

- PHP＋GDだけで日本語CAPTCHAを簡単に作れる
- 海外Bot対策として日本語入力は有効
- ChatGPT 4oレベルでも完全には読めないことを確認
- 今後もAI技術の進化を見据え、定期的なメンテナンスが必要

## 参考
- [PHP公式マニュアル - GD ライブラリ](https://www.php.net/manual/ja/book.image.php)  
　画像生成・描画処理に関する公式ドキュメント。

- [imagettftext - PHP公式関数リファレンス](https://www.php.net/manual/ja/function.imagettftext.php)  
　TrueTypeフォントを使用してテキストを描画する関数。

 - [reCAPTCHA v3（Google公式）](https://cloud.google.com/security/products/recaptcha?hl=ja)  
　Bot対策として広く使われるGoogle提供のCAPTCHAサービス。

## おまけ
今回紹介したCAPTCHAは、バグレポートツール [Alterly](https://alterly.net/) でも実際に使われている仕組みです。  
開発者の作業効率を大きく改善できるサービスなので、ぜひ一度触ってみてください！