cairoを使って簡単に日本語の画像認証を実装する | GCREST_engineerのブログ

GCREST_engineerのブログ

ブログの説明を入力します。

画像認証の昨今
webサービスを運営する上で頭が痛いのがDoS攻撃やリスト型アタックです。
サービスの会員登録またはログイン口に、流出したリストやランダム文字列を用い、
スクリプト(=プログラム)で執拗に攻撃してくるのが特徴です。

その予防策として、割と昔から画像認証という仕組みが設けられました。
ご存知の通り、画像上に表示された文字列を入力→チェックという至ってシンプルなものですが、
アナログなステップを踏むことで機械的な攻撃の防御に力を発揮し、瞬く間に広まりました。

が、最近では認証用の画像すら解析するスクリプトも有り、
その対策としてちょっとやそっとでは読めない画像認証が導入されたりと、
いたちごっこ状態になってきてます。

やる必要はあるの?
どうせ解析されるのなら画像認証要らないのでは?
或いは「ちょっとやそっとでは読めな」くして徒にユーザーの利便性を下げるのもどうか
等の意見もありますが、個人的には画像認証は有効だと思います。

勿論、現状で100%防ぐのは無理ですが、
弊社の事例では海外からのアタックに対し、
日本語の画像認証を導入したところ殆どがブロックできました。

まあ国内向けサービスというので日本語の画像認証が出来たのですが、
ケースバイケースでカスタマイズしていけば、ユーザーの利便性を下げる事無く、
比較的安全なサイト運営が出来ると思います。

導入する上でのポリシー
ポリシーというほどでもありませんが、
ユーザーの利便性を著しく低下させるのは本末転倒です。
ここでは海外からの攻撃に対し、日本語の画像認証を表示する事で対応、
というので検討してみます。

ただ、単に日本語画像を表示するのはセキュリティ的に弱いし、何より面白くないので、
「適当なラインに沿ったテキスト表示」を考えてみましょう。

cairoって何?
画像ソフトではImageMagickGDが有名ですね。(cairoも有名ですが)
どのソフトがベストか、というと、どのソフトもベストだと思います。
皆さんお好きなのを使って宜しいかと。ここではcairoを使ってみます。

cairoは簡単に言ってしまえば、ベクタ形式の描画ソフトです。
座標で管理するので劣化に強く、アンチエイリアスで綺麗な表示が特徴です。
PDFやPNGのSVG(Scalable Vector Graphics)フォーマットのものが典型ですね。

とりあえず入れてみる
CentOS 64ビットのインストール例です。yumで入れちゃうと簡単ですね。

# yum -y install cairo.x86_64 cairo-devel.x86_64

他の画像ソフト同様、cairoでもjava、php、Rubyなど各種モジュールが出ています。
特にRubyのものは素晴らしく、rcairoと言って独自拡張も行っております。
本当はrcairoを使いたいとこですが、執筆者がRubyに不得手な為、
あえてperlでやってみましょう。perl-Cairoをインストールします。

# cpan

cpan[1] > install Cairo

cpanでインストールすると楽勝です。

実際に画像を作成してみる
まず、基本的なプログラムを書いてみます。

#!/usr/bin/perl

use strict;
use Cairo;

my $surface = Cairo::ImageSurface->create('argb32', 200, 200);
my $cr = Cairo::Context->create($surface);

$cr->rectangle(0, 0, 200, 200);
$cr->set_source_rgb(0, 0, 0);
$cr->fill;

$cr->show_page;
$surface->write_to_png('image/output.png');

これを実行し、image/output.pngがどんな画像になるかというと、

縦横200px背景黒画像

縦横200pxの真っ黒な画像が作成されました。ソースを見ていきます。

my $surface = Cairo::ImageSurface->create('argb32', 200, 200);
my $cr = Cairo::Context->create($surface);

まず、元となるSurfaceオブジェクトと、それに描画するContextオブジェクトを定義します。
Cairo::ImageSurface->createの引数は左から順に
アルファ値指定可のRGB32ビット、width、heightです。

$cr->rectangle(0, 0, 200, 200);
$cr->set_source_rgb(0, 0, 0);
$cr->fill;

rectangleで始点座標x、y、終点画像x、yを指定してます。
ここではSurfaceオブジェクトのサイズいっぱいを選択してます。
set_source_rgbで色指定(0~255、全て0指定なので黒です)、
fillで指定範囲を指定した色で塗りつぶします。

曲線を書いてみる
上のソースに以下のコードを挿入します。

...

$cr->curve_to(10,10, 35,190, 185,190);
$cr->set_source_rgb(1, 0, 0);
$cr->stroke;

$cr->show_page;
$surface->write_to_png('image/output.png');

curve_toは始点、中間点、終点の3つの座標を指定して曲線を描画します。
座標は小数点指定も可能です。
set_source_rgbで色を指定(赤です)、strokeで描画します。
実際の画像は、

曲線画像

と、綺麗な曲線が描けました。

テキストその前に
当たり前ですが、日本語を使用する場合、日本語フォントのインストールが必要です。
また、フォントをサービスで利用する場合、商用ライセンスがどうなってるか注意してください。
以下、商用フリーのM+FONT(ttfフォント)のインストール例です。
フォントをダウンロードします。

# cd /usr/local/src
# wget "http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Fmplus-fonts%2F6650%2Fmplus-TESTFLIGHT-058.tar.xz"

xz形式解凍の為にxz-utilsパッケージをインストールし、解凍します。

# yum install xz.x86_64 xz-devel.x86_64
# tar -Jzvf mplus-TESTFLIGHT-058.tar.xz

解凍したttfファイルを所定のフォント格納場所に配置し、反映させます。

# mkdir -p /usr/share/fonts/mplus
# find ./mplus-TESTFLIGHT-058 -name "*.ttf" | xargs -i cp -r {} /usr/share/fonts/mplus/.
# fc-cache -fv

フォントが反映されたか確認。

# fc-list
M+ 1c,M+ 1c black:style=black,Bold
M+ 1c,M+ 1c heavy:style=heavy,Bold
M+ 2p,M+ 2p bold:style=bold
...


テキストを書いてみる
さらに以下のコードを挿入してみます。

...
$cr->curve_to(10,10, 35,190, 185,190);
$cr->set_source_rgb(1, 0, 0);
$cr->stroke;

$cr->move_to(10,60);
$cr->set_font_size(50);
$cr->select_font_face('M+ 1c black', 'normal', 'bold');
$cr->text_path("あまげうまあ");
$cr->set_source_rgb(1, 1, 0);
$cr->fill;


$cr->show_page;
$surface->write_to_png('image/output.png');

move_toで開始点に移動し、
set_font_sizeでフォントサイズ指定、
select_font_faceでフォントの種類を選択します。
text_pathでテキストを指定し、後は色指定、描画(=塗りつぶし)です。

曲線+テキスト画像

はみ出しましたが、テキストが表示されました。尚、文字コードはUTF-8を指定してください。

use utf8;

perlではこうですね。

テキストを曲げてみる
rcairoですと、map_path_ontoというものが用意されていて簡単に実装できるのですが、
perl-Cairoにはありません。
ここを参考にサブルーチンを作成しましょう。

# map_path_onto サブルーチンを作る


といいつつ、ソースを見ると分かる通り、上記を移植するのはかなりの手間です。
幸いperl-Cairo用に移植したサンプルソースがあったので、これを持ってきましょう。
実装したソースが以下となります。

#!/usr/bin/perl

use strict;
use Cairo;
use utf8;

require './twisted-text.pl';

my $surface = Cairo::ImageSurface->create('argb32', 200, 200);
my $cr = Cairo::Context->create($surface);

$cr->rectangle(0, 0, 200, 200);
$cr->set_source_rgb(0, 0, 0);
$cr->fill;

$cr->curve_to(10,10, 35,190, 185,190);
$cr->set_source_rgb(1, 0, 0);
$cr->stroke_preserve;
my $path = $cr->copy_path_flat;


$cr->set_font_size(50);
$cr->select_font_face('M+ 1c black', 'normal', 'bold');
$cr->text_path("あまげうまあ");
map_path_onto($cr, $path);
$cr->set_source_rgb(1, 1, 0);
$cr->fill;

$cr->show_page;
$surface->write_to_png('image/output.png');

requireしているのが持ってきたサンプルソースです。
stroke_preservecopy_path_flatで曲線の構造体を取得し、
持ってきたmap_path_ontoサブルーチンで、
テキストの構造体を曲線の構造体に再マッピングします。

曲線に沿ったテキスト画像

また少しはみ出しましたが、テキストが曲線に沿って表示されました。

カスタマイズ
画像認証の画像生成の箇所だけで終わってしまいましたが、
これをセッションと組み合わせる等して認証ロジックが実装できるかと思います。
(雑ですみません、時間ありましたら追記します)

曲線の傾斜やフォント種別をランダムにするとより強固な認証が出来ますね。
ユーザーの利便性を損なわない様、試し々々やってみてください。

勿論今回紹介できたのはcairoの機能の極々一部で、
その他にもたくさん色々な機能がありますので、
ご興味がありましたら是非色々お試しください。
高品質のグラフィックや、透過や画像合成が簡単に出来ますよ。

また、今回はperlの例でしたが、
基本、モジュールが用意されているどの言語でも似たような構文で実装できると思います。