PHPで、ひらがなCAPTCHA(画像認証)を自作してみた。

PHPでCAPTCHA(画像認証)を自作してみた。

二ヶ月ぐらい前に、PHPのCAPTCHAライブラリ(securimage)を使って実装してみました。

PHPで簡単にCAPTCHA認証(画像認証)を導入する方法

簡単に出来るのは良いのですが、CAPTCHA画像を生成するのが重くてイマイチ感が…。
解析しようと思ったのですが、思ったより規模が大きかったので、むしろ自作してみました。

参考にしたサイト
PHPスクリプト無料配布所 :: PHP.TO
http://php.to/tips/8/

サンプルもダウンロード出来るのですが、わりと文法エラーがあったり(^_^;)

自作するにあたって、突破されづらい画像認証に必要な要素をリストアップしてみました。

1,画像サイズがそれなりに大きい(小さいと総当りが容易になる)
2,フォントを変更する(今回はIPAフォント6種類を使用)
3,文字を少し歪める(PHPではやり方が分からなかったので未実装)
4,文字を回転(画像自体もわずかに回転させた)
5,文字サイズを不規則(基本)
6、文字の表示位置がランダム(基本)
7,文字の色をわずかに変える(ほぼ黒だがRGBを少しイジる)
8,文字数に幅を持たす(5~6文字位が適当かな?)
9,背景画像は単調で無くす(最低限、同じ背景は止める)
10,リロードされたら違う文字にする(Submitされて間違った時も変更する)
11,表示してから制限時間をつける(5分位?これは未実装)

これらを考慮して作成したソースが、こちら。

<?php
// セッション開始
session_start();
//文字化け防止に、言語と文字コードを明示的に指定。
mb_language("Japanese");
mb_internal_encoding("UTF-8");
// 設定項目
define("LOGIN_ENABLE_SEC", 300);// 表示した画像でログインを許可する期間(秒)
define("IMAGE_SIZE_X",150);// 回転前の画像サイズ(横)
define("IMAGE_SIZE_Y", 80);// 回転前の画像サイズ(縦)
define("STRING_NUM_MIN", 5);// 表示する最低文字数
define("STRING_NUM_MAX", 6);// 表示する最大文字数
define("FONT_SIZE_MIN", 12);// 最低フォントサイズ
define("FONT_SIZE_MAX", 20);// 最大フォントサイズ
define("CHAR_ANGLE", 15);// 文字の傾き角度の範囲
define("IMAGE_TYPE", "jpeg");// "jpeg" or "png"
//商用フリーのIPAフォント(6種類)
$arr_font = array("ipaexg.ttf","ipaexm.ttf","ipag.ttf","ipagp.ttf","ipam.ttf","ipamp.ttf");
// 画像に表示する文字列を作る(ひらがなのみ)
// カタカナや平易な漢字を追加するのもあり
$s = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんがぎぐげござじずぜぞだぢづでど";
// 乱数のシードを設定する
mt_srand(hexdec(bin2hex(openssl_random_pseudo_bytes(4))) );
// ランダム文字列を生成
$string = "";
for($i=0; $i<mt_rand(STRING_NUM_MIN, STRING_NUM_MAX); $i++) {
$string .= mb_substr($s,mt_rand(0,mb_strlen($s)-1),1);
}
//画像表示した文字列(正解)をセッション変数に入れる
$_SESSION['captcha_auth'] = $string;
// 画像サイズを指定して、画像オブジェクトを生成
$im = imagecreate(IMAGE_SIZE_X, IMAGE_SIZE_Y);
// 背景色を指定(白色)
imagecolorallocate($im, mt_rand(100,255), mt_rand(100,255), mt_rand(100,255));
$black = imagecolorallocate($im, 0, 0, 0);
$white= imagecolorallocate($im, 255, 255, 255);
$gray = imagecolorallocate($im, 196, 196, 196);
// 可視性を低くさせるために、文字数の2倍のダミーの線を入れる
for($i=0; $i<mb_strlen($string)*2; $i++) {
imageline($im, mt_rand(0,IMAGE_SIZE_X - 1),mt_rand(0,IMAGE_SIZE_Y - 1),mt_rand(0,IMAGE_SIZE_X - 1),mt_rand(0,IMAGE_SIZE_Y - 1), $gray);
}
//ユーザに入力させるための文字を表示
for($i=0; $i<mb_strlen($string); $i++) {
//横軸は順番があるので、文字順にある程度決まっている。
$x = (IMAGE_SIZE_X / mb_strlen($string)) * $i + mt_rand(-3,3);
// 縦軸は、中心部分を基準にランダムに表示
$y = IMAGE_SIZE_Y/2 + mt_rand(-IMAGE_SIZE_Y/4 , IMAGE_SIZE_Y/4);
// 文字の傾き
$r = mt_rand(-CHAR_ANGLE, CHAR_ANGLE);
// 1文字ずつ出力
imagettftext(
$im, // 文字を書く画像
mt_rand(FONT_SIZE_MIN, FONT_SIZE_MAX), // 文字のフォントサイズ
$r, // 文字の角度
$x, $y, // 文字の座標
imagecolorallocate( // 文字の色(ほぼ黒だが、わずかに揺らぎを発生させている)
$im,
mt_rand(0,3),
mt_rand(0,3),
mt_rand(0,3)),
$arr_font[mt_rand(0,count($arr_font)-1)], //文字のフォントを指定
mb_substr(mb_convert_encoding($string, "UTF-8"), $i, 1, "UTF-8") //書き出す文字そのもの
);
}
// 可視性を低くさせるために、わずかに画像を回転させる
$im = imagerotate($im, mt_rand(-2,2), $white);
//画像形式に合わせてヘッダ出力
if (IMAGE_TYPE == "jpeg") {
header('Content-type: image/jpeg');
imagejpeg($im);
} else {
header('Content-type: image/png');
imagepng($im);
}
//最後にリソース廃棄
imagedestroy($im);
?>

実行サンプル
a

このまま実行すると画像を吐き出すだけなので、入力フォーム用HTMLファイルを用意する。

最終的にOK/NGを判定するPHPファイルも用意