前回「
企業での CAPTCHA 利用例」へ
独自 CAPTCHA アプリケーションの開発
ASP.NETには、画像の生成・操作を行うための機能が含まれている。以前のASP 3.0とは異なり、ASP.NETは.NET Frameworkを最大限に活用することができ、外部ライブラリを使わずに動的な画像処理を行うことができる。System.Drawingクラスを使用すると、メモリ内で画像を生成し、その画像を出力ストリームを通じてブラウザ上に描画できる(このプロセスについては、次の分離コードファイル(OntheFly.aspx.vb)を参照のこと)。
このサンプルの目的は、クエリ文字列を通じて渡されたパラメータに基づいて、画像を動的に生成することである。ビットマップ画像を描画するには、HTTPContextを使用する。これは、System.Drawing.Bitmapオブジェクトをインスタンス化するためのブラウザのストリームハンドラである。BitmapコンストラクタはpixelFormatをパラメータとして受け取り、110×50ピクセルの描画用キャンバスを返す。このプロセスはすべてメモリ内で行われ、後でpageContextがフラッシュされるときにブラウザ上に描画されるという点に注意してほしい。次に、ラスタ(つまりビットマップメモリマップ)への描画を行うグラフィックオブジェクトが必要になる。このグラフィックオブジェクトのインスタンスはFillRectangleとDrawStringを呼び出して、キャンバス上に矩形とパラメータテキストを描画する。このキャンバスは、出力ストリームに対してgif MIMEタイプとしてフラッシュされる。実際に出力した画像の例を次の図に示す。
図1.19:動的な画像生成のデモ(手前はIE、後ろはVS IDE)
リスト:OntheFly.aspx.vb
’// Filename: onthefly.aspx.vb
’// Author: Adnan Masood
’// Last updated: 12/07/03
’// Importing the libraries for drawing operations
Imports System
Imports System.Drawing
’// Class OntheFly declaration
PublicClass OntheFly : Inherits System.Web.UI.Page
’//Page_Load event overriding
PrivateSub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
HandlesMyBase.Load
’//Getting HTTPContext
Dim pageContext As System.Web.HttpContext
pageContext = System.Web.HttpContext.Current
’//Reading the parameter
Dim strText AsString = Request("param")
’//Create the memory map
Dim raster As Bitmap
Dim pixFormat As Imaging.PixelFormat
pixFormat = Imaging.PixelFormat.Format32bppArgb
’// Create a memory image with dimensions 110x50
raster = New Bitmap(110, 50, pixFormat)
Dim graphicsObject As Graphics
graphicsObject = Graphics.FromImage(raster)
’// Instantiate object of brush
Dim objBrush As SolidBrush = New SolidBrush(Color.Yellow)
’//Creating a font object with Arial size 10 and bold
’//public Font(string, float, FontStyle)
Dim objFont As Font = New Font("Arial", 10, FontStyle.Bold)
’//Set the background half rectangle blue
’//Method Signature: FillRectangle (const Brush *brush, INT x, INT y, INT width, INT height);
graphicsObject.FillRectangle(New SolidBrush(Color.Blue), 0, 0, 55, 50)
’//Writing the string on image
graphicsObject.DrawString(strText, objFont, objBrush, 0, 0)
’//Flushing the graphics object
graphicsObject.Flush()
’//Setting the response header content MIME type
pageContext.Response.ContentType = "image/gif"
’//Saving the file to output stream to be displayed in browser
raster.Save(pageContext.Response.OutputStream, Imaging.ImageFormat.Gif)
’//Flushing to the Response
pageContext.Response.Flush()
EndSub
EndClass
OntheFly.aspx.vbは、OntheFly.aspxの分離コードファイルである。OntheFly.aspxには次の行が含まれている。
リスト:OntheFly.aspx
<%@ Page Language="vb" Src="OntheFly.aspx.vb" Inherits="OntheFly"%>
図1.20は、出力した画像の例である。パラメータに何か文字列を指定すると、このコードによって画像が生成され、ブラウザ上に描画される。バイナリファイルの中に画像として描画されたテキストは、HTMLコードとは違って簡単に読み取ることができないため、スパムボットで何が書かれているかを判読するためには、特殊なテクニックが必要になる。この単純なサンプルを応用すれば、フォーラムやブログなどの電子メールアドレス保護機能を実現できる。キャッシュ処理を工夫すれば、パフォーマンスが大きく低下することもない。
図1.20:渡されたクエリ文字列パラメータに基づいてASP.NETファイルから動的に生成した画像
辞書ベースのCAPTCHAの実装
今度のサンプルは、本格的なCAPTCHAプログラムの機能を備えているため、先ほどのものより少し長く複雑になっている。このプログラムでは、辞書ベースのCAPTCHAを動的に生成し、画像内のテキストの入力を求めて、相手が人間かどうかの確認を行う。
このプログラムの流れは次のとおりである。ある架空のフォーラムのログインフォームで、ユーザーが人間であるかどうかを確認するために、フォーム下部の画像内のテキストを入力するようユーザーに要求する。プログラムは、用意されているさまざまな画像の中から背景用画像を動的に選択し、「OGDEN’s Basic English」辞書から選択した任意の単語と組み合わせる。OCRでの認識を難しくするために、字体も1文字ごとに変更する。以降では、このプログラムの動作を簡単に説明し、完全なコードリストと辞書データベースファイルを紹介する。
ユーザーが間違った単語を入力すると、次の画面に進むことができず、エラーメッセージが表示される。
図2.2:フォーラムへのログイン時に表示されるエラーの例
ボットが再試行しようとすると、今度は別の背景と別の単語を使用した新しい画像が表示される。
アプリケーション設計者は、再試行を何回まで認めるかを検討しなければならない。ユーザーが再試行した回数を記録しておき、このプログラムがセキュリティ対策ではなく一般ユーザーに対する障壁になっていないかどうかを確認する必要がある。また、障害者のために、代替策(音声を使用するなど)も用意しておいた方がよい。
ユーザーが正しい単語を入力した場合、このプログラムはユーザーのアクセスを許可する。その前に別の処理を挿入することもできる。
図2.4:フォーラムへのログインが成功した場合の例
それでは、このアプリケーションの内容を詳しく見ていくことにしよう。
次の図は、OGDENのBasic English辞書をデータベース化したものである。各列に同じアルファベットから始まる単語が収録されており、ここからCAPTCHA画像に組み込む単語をランダムに選択できる。
図:「OGDEN’s Basic English」辞書をアプリケーション用にデータベース化したもの(dictionarydb)
このアプリケーションには次の3つのメインファイルがある。
captchaForm.aspx
このファイルには、辞書内の単語をランダムに選択してcaptcha.aspxに渡すためのコードが含まれている。captcha.aspxの分離コードファイルであるcaptcha.aspx.vbは、渡された単語を処理して、ブラウザ上に画像を描画する。このファイルには、フォームを処理するコードと、ユーザーが入力したCAPTCHAの値を検証するコードも含まれている。さらに、dictionary.mdbへの接続、ランダムな単語の読み取り、セッションの処理を行うコードも含まれている。
captcha.aspx.vb
これはcaptcha.aspxの分離コードファイルである。画像生成の処理はこのファイルが担当する。このファイルはbackgroundsフォルダ内の背景パターンをランダムに選択し、captchaform.aspxから渡されたテキスト文字列を書き込む。さらに、機械による読み取りを難しくするために、字体とサイズをランダムに選択する。
dictionary.mdb
このAccessデータベースリポジトリには、OGDENのBasic English辞書の項目が格納されている。
各所のコメントを追っていけば、このコードでどんな処理をしているかはすぐにわかるだろう。
リスト:captchaForm.aspx
<!--
’// Filename: captchaForm.aspx
’// Author: Adnan Masood
’// Last updated: 12/08/03
-->
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<%@ Page Language="C#" debug=true%>
<HTML>
<head>
<script runat="server">
// Pageload event
void Page_Load(object Source, EventArgs E)
{
// If form is not posted than assign the selectWord return to session
if (!IsPostBack)
Session["param"] =selectWord();
// Else check if session’s value is same as that of Captcha’s entered value
// in the form and assign appropriate message to innertHtml of label
else
{
if (String.Compare(Convert.ToString(Session["param"]),Convert.ToString(Request.Form["CAPTCHA"]))==0)
lblstatus.InnerHtml= "Thanks for Registeration with our Forums";
else
lblstatus.InnerHtml= "Error Occured! Value of Image is not correct, " +
"please <a href=captchaform.aspx> re-enter </a>";
}
}
// Select word method
// Return type String of random word from dictionary
// Dictionary is based on OGDEN’s BASIC ENGLISH
// http://ogden.basic-english.org/basiceng.html
public String selectWord () {
// The Connection string referencing the MDB file
String ConnectionString = "Provider=Microsoft.Jet.OleDb.4.0;Data Source="
+ Server.MapPath("dictionary.mdb") + ";";
// Datareader object
OleDbDataReader objReader;
// Creating an array of 26 characters (alphabets in dictionary database as columns)
char[] columns = newchar[26];
// Adding the column names in the array
// uses the ASCII character conversion for selecting values
// from A- Z
for (int a=65; a<65+26; a++)
columns[a-65] = (char)a;
// Query String for selecting a random column from spelling list database
String QuerySQL = "SELECT " + columns[(new Random().Next(26))] + " FROM spellList";
// Opening the connection
OleDbConnection objConn = new OleDbConnection(ConnectionString);
// Creating new command object
OleDbCommand objCmd = new OleDbCommand();
// Assigning command text
objCmd.CommandText = QuerySQL;
// Assigning the connection to command object connection attribute
objCmd.Connection = objConn;
// Instantiating a random class object
Random randomSeed = new Random();
// Creating a random seed selector
int randomSeedSelector=0;
// An string character with maximum capacity for dictionary column
String[] selectedIndex = new String[700];
String str = "";
// This code segment opens the connection and read the dictionary
try
{
objConn.Open();
objReader = objCmd.ExecuteReader();
while (objReader.Read()) {
str = objReader.GetValue(0).ToString();
if (str.Length != 0) {
selectedIndex[randomSeedSelector] =str;
randomSeedSelector++;
}
}// Ends While
str = selectedIndex[randomSeed.Next(randomSeedSelector)];
} // Ends Try
catch (Exception Err)
{
// The Error Catching operations
}
finally
{
objConn.Close();
}
// Returns the selected string
return (str);
}// ends function
</script>
</head>
<body>
<% //Checking for postback for initial processing of form.
if (!IsPostBack) { %>
<form runat="server">
<P align="left"><STRONG><FONT size="5">Signup for Forums</FONT></STRONG></P>
<P>
<TABLE id="Table1" height="200" cellSpacing="1" cellPadding="1" width="704" border="1">
<TR>
<TD>
<P>Name</P>
</TD>
<TD><INPUT id="Text1" type="text" name="name"></TD>
</TR>
<TR>
<TD>Email Address</TD>
<TD><INPUT id="Text2" type="text" name="email"></TD>
</TR>
<TR>
<TD>
<P><IMG src="http://localhost/captcha/example2/captcha.aspx"></P>
<P>Enter the text above into the box on right.</P>
</TD>
<TD><INPUT id="Text3" type="text" name="CAPTCHA"></TD>
</TR>
<TR>
<TD>
<INPUT id="Submit1" type="submit" value="Submit" name="Submit1">
<INPUT id="Reset1" type="reset" value="Reset" name="Reset1">
</TD>
<TD></TD>
</TR>
</TABLE>
</P>
</form>
<% }
//Setting lable’s status accordingly
else
{ %>
<td id="lblstatus" runat="server"></td>
<% }%>
</body>
</HTML>
リスト:captcha.aspx.vb
’// Filename: captcha.aspx.vb
’// Author: Adnan Masood
’// Last updated: 12/08/03
Imports System
Imports System.Drawing
Imports System.String
Imports System.Drawing.Image
namespace com.axisebusiness.OnlineImage
’// Importing the libraries for drawing operations
’// Class OntheFly declaration
PublicClass CAPTCHA : Inherits System.Web.UI.Page
’//Page_Load event overriding
PrivateSub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
HandlesMyBase.Load
’//Getting HTTPContext
Dim pageContext As System.Web.HttpContext
pageContext = System.Web.HttpContext.Current
’//Reading the parameter from session this time
Dim strText AsString = Session("param")
’//Create the memory map
Dim raster As Bitmap
Dim pixFormat As Imaging.PixelFormat
pixFormat = Imaging.PixelFormat.Format32bppArgb
’// Select an memory image from file of 290x80px
’// in the backgrounds folder named backX.jpg
Dim graphicsObject As Graphics
Dim imageObject as System.Drawing.Image =
System.Drawing.Image.FromFile(Server.Mappath("backgroundsack" &
new Random().Next(5) & ".jpg"))
’// Creating the raster image object
raster = new Bitmap (imageObject)
’//Creating graphics object
graphicsObject = Graphics.FromImage(raster)
’// Instantiate object of brush with black color
Dim objBrush As SolidBrush = New SolidBrush(Color.Black)
Dim objFont As Font
dim a asInteger, myFont asString, str asString
’//Creating an array for most readable yet cryptic fonts for OCR’s
’// This is entirely up to developer’s discretion
dim crypticFonts(11)
crypticFonts (0) = "Arial"
crypticFonts (1) = "Verdana"
crypticFonts (2) = "Comic Sans MS"
crypticFonts (3) = "Impact"
crypticFonts (4) = "Haettenschweiler"
crypticFonts (5) = "Lucida Sans Unicode"
crypticFonts (6) = "Garamond"
crypticFonts (7) = "Courier New"
crypticFonts (8) = "Book Antiqua"
crypticFonts (9) = "Arial Narrow"
crypticFonts (10) = "Estrangelo Edessa"
’//Loop to write the characters on image
’// with different fonts.
For a=0 to strText.Length-1
myFont = crypticFonts(new Random().Next(a))
objFont = New Font(myFont, 20, new FontStyle().Bold)
str = strText.substring(a, 1)
graphicsObject.DrawString(str, objFont, objBrush, a*20, 35)
graphicsObject.Flush()
next
’//Flushing the graphics object
graphicsObject.Flush()
’//Setting the response header content MIME type
pageContext.Response.ContentType = "image/gif"
’//Saving the file to output stream to be displayed in browser
raster.Save(pageContext.Response.OutputStream, Imaging.ImageFormat.Gif)
’//Flushing to the Response
pageContext.Response.Flush()
EndSub
EndClass
Endnamespace
Captcha.aspxファイルには次のように記述されている。
リスト:Captcha.aspx
<%@ Page Language="C#" Src="Captcha.aspx.vb" Inherits="com.axisebusiness.OnlineImage.Captcha"%>
お気付きのとおり、このコードは1つのプログラム内で複数の言語(この場合はC#とVB.NET)を使用する方法のサンプルでもある。このプログラムで生成したCAPTCHAは絶対に破られないとは言えないが、通常のスパムボットへの対策としてはかなり有効である。
次回「
CAPTCHA を Web サービスから配布または呼び出す方法」へ