perl で Captcha 認証(セキュリティ画像)をやる方法について

ユーザ登録とかでロボット等でのスパム登録を防止したりって用途に使われているアレです。こんなヤツ。

img01.jpg

最近はブログのコメントとかのスパム防止でもよく見かけます。まずは基礎知識。このような画像で認証を行うことを Captcha っていいます。wikipedia の情報を引用すると、

CAPTCHA(キャプチャ、"Completely Automated Public Turing test to tell Computers and Humans Apart"; コンピュータと人間を区別する完全に自動化された公開チューリングテスト)は チャレンジ/レスポンス型テストの一種で、ユーザが人間であるかどうかを決定する計算処理に使われる。この用語はカーネギーメロン大学のLuis von Ahn、マヌエル・ブラム、Nicholas J. Hopper、IBMのJohn Langfordによって2000年に造られた。日本でよく使われる「画像認証」はその一つ。
もっとも一般的な画像によるCAPTCHAの場合、次のように画像に記されている文字や数字を読み取ることができるか否かによって人間と機械を判別する。
  1. CAPTCHAシステムは、ランダムな文字や数字の列を画面に表示する。表示される文字は歪んでいたり一部が覆い隠されていたりして、機械が自動的に読み取ることは難しい。
  2. ユーザーは画面に描かれている文字の列を読み取り、同じ文字列をシステムに入力する。
  3. システムが表示した文字列とユーザーが打ち込んだ文字列が一致していれば、ユーザーは歪んだ画像を認識する能力を持っていると考えられる。システムはそのユーザーが人間であると推測する。


これを perl で実装するには・・・なんてことをここ数日調べていたので情報まとめています。なかなかこの手の日本語情報が少ないので誰かの役に立つかなと。ある意味 http://perl-users.jp/ 向けだったりと。

- スポンサーリンク -

長くなりそうなので、2〜3エントリに渡って説明をしていきたいと思っています。まずは perl で captcha をやりたい場合にはベースとなるモジュールが2つあります。

Authen::Captcha

結構古く(2003年)からあるモジュール。昔はこれを使うのが流行っていたみたい。現在は更新も止まっているようだし、キャプチャ画像生成において、固定の背景とフォント画像を用いて画像生成するため、セキュリティ的にアレでしょって感じで使われなくなっているようです。ちなみにキャプチャ画像とユーザが入力した文字の整合性チェックまでモジュールが面倒を見てくれています。

使用を推奨しないよって意見が多いようなので、説明もここまで。僕自身もそれほど解析をしてません。

GD::SecurityImage

Authen::Captcha のあとがま。互換モードとか実装されていて乗り換えも意識して作られています。こちらは、TrueType フォントが使えるので自分の好きなフォントでキャプチャ画像を生成できます。背景画像は設定できないけど、ドットやラインなどがランダムで描画される仕掛けになっています。

ちなみに、こちらのモジュールはあくまでキャプチャ画像の生成と表示を担当するのみ。キャプチャ画像とユーザが入力した文字の整合性チェックはユーザ側が実装するポリシーになっています。そこら辺はまた別途説明。

さて、このモジュール。引数がいろいろあって何をどうすると、どんな画像が生成されるのかがちょっと把握しづらいです。おまけに僕が探していたタイプのキャプチャ画像のスタイル指定ができなかったので、勝手に拡張しました。
※フォントのサイズをランダムで変更 + 背景画像にランダムライン + フォント描画開始位置の指定 を加えました。
※この辺は後ほど作者に patch を送りたいけど英語めんどい。

そんなわけで、このエントリは、このオプションをこうすると、こんな画像が生成されますよってサンプルまでで終了。そこから先はまた次回で。このプログラムが GD::SecurityImage を勉強する素材としてもちょうど良いかと思う次第です。

GD::SecurityImage でキャプチャ画像を生成してみる

※ptrange / style=line" は勝手に拡張した部分。それ以外はオリジナルに実装されているオプション。全てのオプションを網羅したわけではありませんけど、これで 80% 程度は網羅されているはず。何をどう設定するか把握するのにお役立て下さい。


まずはキャプチャ画像表示プログラムはこんな感じ。form から受け取った値を元に GD::SecurityImage の設定値を決めて画像生成やってます。

#!/usr/local/perl

use strict;
use warnings;
use GD::SecurityImage;  
#use GD::SecurityImage backend => 'Magick';  # ImageMagick が入っている方はこちらで
use HTTP::Date;
use CGI;

my $q       = CGI->new;
my $fontcol = $q->param('fontcolor') || '#000000';
my $linecol = $q->param('linecolor') || '#555555';
my $style   = $q->param('style') || 'line';

my $config = {
    session_name => 'captcha_string',
    new          => {
        lines     => $q->param('lines')     || 20,
        angle     => $q->param('angle')     || 0,
        scramble  => $q->param('scramble')  || 0,
        ptsize    => $q->param('ptsize')    || 20,
        ptrange   => $q->param('ptrange')   || 0,
        thickness => $q->param('thickness') || 1,
        width     => 300,
        height    => 100,
        rnd_data  => [ 'A' .. 'Z' ],
        margin    => [ 10, 10 ],
        bgcolor   => $q->param('bgcolor') || '#eeeeee',
        font      => "フォント名.ttf",
    },
    create => [ "ttf", $style, $fontcol, $linecol ],
    particle => [ $q->param('particle1') || 100, $q->param('particle2') || 1 ],
    out => { force => "jpeg" },
};

my $image = GD::SecurityImage->new( %{ $config->{new} } );
$image->random();
$image->create( @{ $config->{create} } );
$image->particle( @{ $config->{particle} } );
my ( $image_data, $mime_type, $random_string ) = $image->out( %{ $config->{out} } );

print $q->header(
    -type          => qq{image/$mime_type},
    -expires       => time(),
    -last_modified => HTTP::Date::time2str,
    -pragma        => 'no-cache',
    -cache_control => 'no-cache'
);
print $image_data;

ちなみに、キャプチャ画像を生成するにあたって、フォントの色等には注意が必要です。ちょっとした画像ソフトでフィルタしたら機械的に文字認識が可能なんて事にならないように。なんて事は次回以降に説明したいと思います。

- スポンサーリンク -