Sunday, February 27, 2011

How to develop CAPTCHA functionality in ASP.NET

Ever wondered after seeing those little curvy digits in neat boxes followed by input saying “Enter the above text:”, where do they come from?


 This usually happens when you register for a free online service like an email-id or FTP service. Such validation may also happen before you submit any comment on a blog or website. You might wonder, after all what is the point of this?









The idea is to incorporate a validation to protect from spam and stop internet bots from automatically submitting forms on your website. CAPTCHA stands for “Completely Automated Public Turing Test To Tell Computers and Humans Apart”.


Computers have automated most of the tasks that humans do on the net, and creating a program to impersonate a human by filling some text-boxes on a HTML form is a very simple task in a language like Java, VB.net or C#. Such programs are called auto-bots or internet-bots that are usually used by spammers.
Bots are also used to manipulate results of online-polls. For instance, in November 1999, Slashdot released an online poll asking which was the best graduate school to study computer science. IP addresses of voters were recorded in order to prevent single users from voting more than once. However, students at Carnegie Mellon found a way to stuff the ballots using programs that voted for CMU thousands of times. CMU's score started growing rapidly. The next day, students at MIT wrote their own program and the poll became a contest between voting bots. MIT finished with 21,156 votes, Carnegie Mellon with 21,032 and every other school with less than 1,000!

Can the result of any online poll be trusted? Not unless the poll ensures that only humans can vote. And that can be done by using CAPTCHA. It is not necessary that CAPTCHA should be a manual image verification. It could even be a common sense question like “What is the capital of India?” or “What is one plus one?”.
Such simple questions for humans are still a challenge for most AI programs, and it is very difficult to write a generic bot to create such a complex task that involves natural language processing. However, if somebody is specifically targeting your website, this question can be easily bypassed as the answer will be “Delhi” each time the form is submitted.
Moreover, for a small website, it is difficult to come up with a dictionary of so many questions and switch between them randomly. Ultimately each one will be deciphered after several attempts by the bot.
The only full-proof solution is image based verification. Reading curved letters is one of the few tasks done on a computer, in which even the present best technology cannot beat the cortex. Consider the below image:




Reading the above image involves three tasks (from a computer’s point of view):
1. Pre-processing: Removing the background clutter and noise.
2. Segmentation: Splitting the image into regions which contain a single character.
3. Classification: Identifying the character in each region.
In fact, tasks 1 and 3 are pretty easy for a computer. This is being done since ages through a technology called OCR (Optical Character Recognition). Only the task 2, of differentiating each character still remains a challenge for even the most advanced AI programs. On the other hand, it is a piece of cake, for even the most dumbest of humans.

Now, let us get down to the technical task of creating such a random image, and verifying the same in ASP.net. Assuming that you know the basics of ASP.net like developing web-forms, code-behind pages, handling session variables, etc. I’ll start with the particulars of creating this functionality. The language I’m using is c-sharp, but VB.net alternatives also exist for those who are interested. The .net framework being used in the example is 2.0 and development tool is Visual Studio 2005.
Basically, we need one web-form (.aspx) and one class (.cs) file to develop this functionality. Although, you can merge both into one, I suggest you keep the bitmap-creation part in the class, while rendering the output to the browser using .aspx. Apart from these two, there will be a third web-form that uses the CAPTCHA functionality. This will typically be a login page or a comment page on the blog.

CaptchaImage.cs:


public int height = 0;
    public int width = 0;
    public string familyname = "Verdana";
    public string text="";
    public Bitmap image;

    private Random random = new Random();

    public void dispose()
    {
        image = null;
        random = null;
 }
    public void GenerateImage()
    {
        Bitmap bt = new Bitmap(this.width,this.height,System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        
        //Create the graphics object
        Graphics g = Graphics.FromImage(bt);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        Rectangle rect = new Rectangle(0, 0, this.width, this.height);

        //Fill the background
        HatchBrush hb = new HatchBrush(
            HatchStyle.SmallConfetti,
            Color.LightGray,
            Color.White);
        g.FillRectangle(hb, rect);

        //Text font
        SizeF size;
        float fontsize = rect.Height + 1;
        Font font;

        //Adjust the text size until the text fits within the image
        do
        {
            fontsize--;
            font = new Font(
            this.familyname,
            fontsize,
            FontStyle.Bold
                );
            size = g.MeasureString(text,font);
        } while (size.Width > rect.Width);

        //Text format
        StringFormat format = new StringFormat();
        format.Alignment = StringAlignment.Center;
        format.LineAlignment = StringAlignment.Center;

        //Create text path & warp it randomly
        GraphicsPath path = new GraphicsPath();
        path.AddString(text, font.FontFamily, (int)font.Style, font.Size, rect, format);
        float v = 4F;
        PointF[] points = 
        {
        new PointF(this.random.Next(rect.Width)/v,this.random.Next(rect.Height)/v),
        new PointF(rect.Width - this.random.Next(rect.Width)/v,this.random.Next(rect.Height)/v),
        new PointF(this.random.Next(rect.Width)/v,rect.Height- this.random.Next(rect.Height)/v),
        new PointF(this.width- this.random.Next(rect.Width)/v,this.height- this.random.Next(rect.Height)/v)
        };
        Matrix matrix = new Matrix();
        matrix.Translate(0F, 0F);
        path.Warp(points, rect, matrix,WarpMode.Perspective,0F);

        //Draw text
        //hb = new HatchBrush(HatchStyle.LargeConfetti, Color.LightGray, Color.DarkGray);
        hb = new HatchBrush(HatchStyle.LargeConfetti, Color.LightGray, Color.DarkGray);
        g.FillPath(hb, path);

        //Random noise
        int m = Math.Max(rect.Width, rect.Height);
        for (int i = 0; i < (int)(rect.Width * rect.Height / 30F); i++)
        {
            int x = this.random.Next(rect.Width);
            int y = this.random.Next(rect.Height);
            int w = this.random.Next(m/50);
            int h = this.random.Next(m/50);
            g.FillEllipse(hb, x, y, w, h);
        }
        
        //Clean up
        font.Dispose();
        hb.Dispose();
        g.Dispose();

        //set image
        this.image = bt;
    }



Above code is the heart of your CAPTCHA functionality. Input is provided to this object using public variables like height, width and text. A graphics object is used to create the bitmap. Hatch brush is used to first fill the background, and later draw the text. The curved appearance to the end result is given by the warp() method of the graphics path, and also by the style of the Hatch brush.

Default.aspx.cs:


protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            Session["CaptchaText"] = GenerateRandomCode();
        }
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        if (TextBox1.Text == Session["CaptchaText"].ToString())
        {
            Label1.Text = "Validation successful";
        }
        else 
        {
            Label1.Text = "Incorrect Captcha";
            Session["CaptchaText"] = GenerateRandomCode();
        }
    }
    public static string GenerateRandomCode()
    {
        char c = (char)0; int dice = 0;
        string res = "";
        for (int i = 0; i <= 3; i++)
        {
            dice = random.Next(2,2);
            switch (dice)
            {
                case 1: //A
                    c = (char)random.Next(65, 90);
                    break;
                case 2: //a
                    c = (char)random.Next(97, 122);
                    break;
                case 3: //1
                    c = (char)random.Next(48, 57);
                    break;
            }

            res += c.ToString();
        }

        return res;
    }


This will be your login or comment web-form that uses the CAPTCHA functionality. In the code-behind file, you need to generate a random Captcha and store in a session variable in the page-load. Here, it is important to check that generation is done only if it is not a PostBack, else it is a case of validating existing Captcha posted by the user.


Default.aspx:



<asp:Image ID="Image1" runat="server" ImageUrl="~/Views/Home/CaptchaJPEG.aspx" /><br />
    <br />
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><br />
    <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Submit" /><br />
    <br />
    <asp:Label ID="Label1" runat="server"></asp:Label>


In Default.aspx i.e. the Front-end part, we just take normal image control. Instead of assigning its ImageUrl property to a .jpg/.gif, we assign it to an .aspx page, CaptchaJPEG.aspx. This web-form will do rendering the bitmap part. It will call the CaptchaImage class’s GenerateImage() function to generate the bitmap using the text from the session variable.

CaptchaJPEG.aspx.cs:


CaptchaImage captcha = new CaptchaImage();
    protected void Page_Load(object sender, EventArgs e)
    {
        captcha.width = 250;//200;
        captcha.height = 50;//50;
        captcha.text = this.Session["CaptchaText"].ToString();
        captcha.GenerateImage();

        captcha.image.Save(this.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
    }


Once you hook up the above things correctly, all you have to do is start your Default.aspx in a web browser. If everything goes right, you will see a nice piece of CAPTCHA validation functionality as seen below:

 

1 comment:

  1. This code is fine but give an compilation error in a both the page that an "Inherit" attribute does not match ,code inherits the page or usercontrol class....
    Please give corrective solution to this problem.

    ReplyDelete