Sunday, May 20, 2007

Image-based CAPTCHA. Demo Project

Demo project that illustrates the idea of the image-based CAPTCHA.

The code overview

The proposed image-based CAPTCHA control works in such a way: a visitor sees a picture with a distorted part and he has to click elsewhere in the anomalous region boundaries. The point he clicks on will be marked with a red spot.

The control fulfils a double functionality, it renders its HTML content and forms the picture itself. It has two child controls: an image, and a hidden field that serves to store coordinates of the visitor's chosen point. The image URL forms by adding the special parameter to the currently requested URL. When the request to this new URL comes, the control interrupts the usual process of page loading and writes the image as a byte array in the response.
protected override void OnInit(EventArgs e)
{
if (HttpContext.Current.Request[queryKey] != null)
DrawImage();
.....
}
private void DrawImage()
{
Bitmap bitmap;

//the image creation goes here
....

HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = "image/jpg";
bitmap.Save(HttpContext.Current.Response.OutputStream, ImageFormat.Jpeg);
HttpContext.Current.Response.End();
}

This approach allows to compound all the functionality in a single control. In a heavily loaded environment, it is better to take out the image creation elsewhere to another place - for example, to the HttpHandler - to avoid complicating the creation of the page where the control is placed on.

The coordinates of the point where the visitor clicks is calculated and visualized by means of JavaScript (tested on IE 6.0, Firefox 1.0+, Opera 9.1). They are stored in the hidden field in order to be accessible on the server side.
function captchaClicked(hidID,e)
{
var sender = e.target || e.srcElement;
var offsetX, offsetY;

//the calculation of the the clicked point's coordinates
if (e.offsetX) //IE
{
offsetX = e.offsetX;
offsetY = e.offsetY;
}
else if (e.pageX) //Firefox, Opera
{
var left = sender.offsetLeft;
var top = sender.offsetTop;
var parentNode = sender.offsetParent;
while(parentNode != null && parentNode.offsetLeft != null && parentNode.offsetTop != null){
left += parentNode.offsetLeft;
top += parentNode.offsetTop;
parentNode = parentNode.offsetParent;
}
offsetX = e.pageX - left;
offsetY = e.pageY - top;
}

//storing of the coordinates
document.getElementById(hidID).value = offsetX+","+offsetY;

//the creation of the little red spot to mark the clicked point
var spot = document.getElementById("spotOnCaptha");
if (!spot)
{
spot = document.createElement("span");
spot.id = "spotOnCaptha";
spot.style.height = "3px";
spot.style.width = "3px";
spot.style.fontSize="1px";
spot.style.backgroundColor="red";
spot.style.position="absolute";
spot.style.zIndex = 100;
sender.parentNode.appendChild(spot);
}

//positioning of the spot
spot.style.left = e.pageX || (event.clientX + document.body.scrollLeft);
spot.style.top = e.pageY || (event.clientY + document.body.scrollTop);
}

Now, about the CAPTCHA image creation. In the loaded template picture, a rectangle with randomly defined coordinates is selected. Then, the coordinates are saved in the Session. It is possible to store them in the ViewState, but in this case, they have to be encoded because the ViewState is accessible on the client side. Then, the image in the rectangle boundaries is stretched (any other distortion may be used, the only condition - it has to be notable for the site visitor).

Also, here is another problem. It is possible to compare the template (original) image and the final distorted image pixel by pixel in order to find the distorted area. To avoid this possibility, the template image is also changed in a random way. In the example below, it is stretched or compressed on a random scale.
//template image loading
using (System.Drawing.Image img = System.Drawing.Image.FromFile(this.Page.MapPath(TemplateImageUrl)))
{
//CAPCTHA image size is smaller then the template image one.
int captchaWidth = (int)(img.Width * 0.9);
int captchaHeight = (int)(img.Height * 0.9);
bitmap = new Bitmap(captchaWidth, captchaHeight);
Graphics g = Graphics.FromImage(bitmap);
//the rectangle dimension
int rectWidth = 20;
int rectHeight = 20;
Random r = new Random();
//the original (template) image is stretched or shrunken in a random way
float scaleX = 1 + r.Next(-100, 100) / 1000f;
float scaleY = 1 + r.Next(-100, 100) / 1000f;
g.ScaleTransform(scaleX, scaleY);
g.DrawImage(img, 0, 0, img.Width, img.Height);
g.ResetTransform();

//the rectangle coordinates are selected in a random way too
int x = r.Next(0, captchaWidth - rectWidth);
int y = r.Next(0, captchaHeight - rectHeight);
Rectangle distortedRect = new Rectangle(x, y, rectWidth, rectHeight);
HttpContext.Current.Session["ImgCAPTCHA.coords"] = distortedRect;
rectWidth = rectWidth + 10;
rectHeight = rectHeight + 10;
if (x + rectWidth > captchaWidth)
x = captchaWidth - rectWidth;
if (y + rectHeight > captchaHeight)
y = captchaHeight - rectHeight;
//draw distorted part of the image
g.DrawImage(img, distortedRect, new Rectangle(x, y, rectWidth, rectHeight), GraphicsUnit.Pixel);
g.DrawRectangle(Pens.Black, 0, 0, captchaWidth-1, captchaHeight-1);
}

Maybe the proposed image creation algorithm looks imperfect. Indeed, it is. I want to say that this article is rather the idea presentation then the control's description (it is just an example).

How to use

In order to use the control it has to assign the path of the template image - TemplateImageUrl property. The result of the CAPTCHA control action is a value of IsValid property. Besides here is the ability to lock user after the fixed number of failed attempts per session (FailedAttemptsBeforeLocking, IsLocked).

Download code - 34Kb

Update: I left the demo project for the demonstration how to implement this kind of CAPTCHAs. It is functional but not too optimal. Do not judge this code harshly :), it was written offhand specifically to illustrate this article that was published May 20, 2007. The control was greatly changed since then. If you want to include such CAPTCHA in your project then better to use ready Image-Based Bot Detector control that is available for download since Jun 24, 2007. It is free, with advanced facilities, tested in the most of modern browsers.

No comments:

Post a Comment