Roger Ngo's Website

Personal thoughts about life and tech written down.

Creating a Simple Captcha in .NET

Well, I finally implemented some sort of CAPTCHA system into the comments form. For the longest time, I have gotten away with it simply because I don't get that much traffic. But I've decided to spend my Sunday morning hooking one up to this darn blog, and it was quite educational.

The source code can be downloaded here: https://github.com/urbanspr1nter/RNCaptcha

CAPTCHA

The whole point of a CAPTCHA is that a field input is required to verify whether or not the submitter is human or machine. By making a string of text display as an image for the user to interpret and validate, we can make it harder for a bot to parse this string and get away with things such as spamming with comments. Sometimes, having a string of text in the form of an image is not enough, and we need to get creative with things such as adding shapes and lines to the background, distorting the text, blurring, adding transparency and so on in hopes of being able to fool the machine while still keeping things readable for the human.

CAPTCHA example

An example of a CAPTCHA with text distortion.

Design

My attempt in creating a CAPTCHA is pretty naive, but it is still better than nothing. The plan was to just make it a tad bit harder for some machine to be able to freely insert content into a database. Therefore I only had a few simple requirements.

  1. The randomly generated key will consist a universe of alphanumeric characters: A-Za-z0-9 and special characters !@#$%^&*.
  2. Display the string of text as an image.
  3. Add minor obstructions such as randomly generated lines of basic colors and transparency.
  4. Output the generated image as HTML markup in the form of a Base64 encoded string.

Okay, so all that is pretty simple, and so we can get started.

Implementation

If you just want to try out the source code, you can grab it from my Git repository here: https://github.com/urbanspr1nter/RNCaptcha

Basically, the first step is to come up with a way to generate a random "key". This key is essentially a string that will be displayed in our CAPTCHA. The function below is sufficient for our needs for generating an 8 character key that will be used for our CAPTCHA.

public static string GenerateRandomString()
{
  string universe = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$#%^&*";
  string result = "";

  Random rand = new Random();

  for (int i = 0; i < 8; i++)
  {
   int randomIndex = rand.Next(0, universe.Length - 1);

   result = result + universe.Substring(randomIndex, 1);
  }

 return result;
}

The next step is to actually generate an image. Our function will create an image with our given key and return this image encoded as a Base64 string.

public string GetCaptcha()
{
 Bitmap bmp = new Bitmap(this.width, this.height);
 RectangleF rect = new RectangleF(8, 5, 0, 0);
 Graphics g = Graphics.FromImage(bmp);

 g.Clear(Color.FromArgb(240,240,240));
 g.SmoothingMode = SmoothingMode.AntiAlias;
 g.InterpolationMode = InterpolationMode.HighQualityBicubic;
 g.PixelOffsetMode = PixelOffsetMode.HighQuality;

 g.DrawString(this.answer, new Font("Tahoma", this.fontSize, FontStyle.Bold ^ FontStyle.Italic), Brushes.CornflowerBlue, rect);

 Random rand = new Random();

 for (int i = 0; i < 10; i++)
 {
  int x0 = rand.Next(0, this.width);
  int y0 = rand.Next(0, this.height);
  int x1 = rand.Next(0, this.width);
  int y1 = rand.Next(0, this.height);

  g.DrawLine(new Pen(Color.FromArgb(192, 75, 75, 75)), x0, y0, x1, y1);
 }

 for (int i = 0; i < 10; i++)
 {
  int x0 = rand.Next(0, this.width);
  int y0 = rand.Next(0, this.height);
  int x1 = rand.Next(0, this.width);
  int y1 = rand.Next(0, this.height);

  g.DrawLine(new Pen(Color.FromArgb(192, 125, 75, 255)), x0, y0, x1, y1);
 }

 MemoryStream mem = new MemoryStream();
 bmp.Save(mem, ImageFormat.Png);

 g.Dispose();
 bmp.Dispose();

 return Convert.ToBase64String(mem.ToArray());
}
        

So obviously, from above, it really looks like there is a lot going on here. Let us break it down.

In lines 3-5, we basically declare and initialize our objects needed to be able to perform basic .NET drawing functions. Our Bitmap object is created with the specified width and height class members. We also create a RectangleF object which will come into play later along with a Graphics object created from the Bitmap we have just initialized.

Bitmap bmp = new Bitmap(this.width, this.height);
RectangleF rect = new RectangleF(8, 5, 0, 0);
Graphics g = Graphics.FromImage(bmp);

The following lines 7-10, we set our Graphics object to have special rendering modes. The first thing we do here is clear our graphics panel and fill it with a specific color. In our case, this is going to be a light grey color. We also set the rendering quality to use anti-aliasing along with high quality bicubic interpolation.

g.Clear(Color.FromArgb(240,240,240));
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;

The next line after this, is the special line that will render our generated key into the bitmap image. All we do here is call the DrawString() method with the specified key as the first parameter and other attributes subsequently. Note, since we cannot pass int multiple font styles such as bolds and italics, I decided to pass in an XOR of the two to combine the bits to generate the bold-italic effect.

Now, going back our RectangleF object was created intially to basically tell us where exactly in our plane we must begin drawing our text. From the previous piece of code, I told it to start 8px from the 0 of the x-axis and 5px from the 0 of the y-axis.

g.DrawString(this.answer, new Font("Tahoma", this.fontSize, FontStyle.Bold ^ FontStyle.Italic), Brushes.CornflowerBlue, rect);

So, from here we have basically done all the hard work in getting our key into an image format. What we need to do next is add a little extra to the image to make it slightly harder to interpret. So we just begin drawing random lines at various places along our space. I decided to draw the lines in two sequences. One using dark grey lines and the other with purple-ish lines.

 Random rand = new Random();

 for (int i = 0; i < 10; i++)
 {
  int x0 = rand.Next(0, this.width);
  int y0 = rand.Next(0, this.height);
  int x1 = rand.Next(0, this.width);
  int y1 = rand.Next(0, this.height);

  g.DrawLine(new Pen(Color.FromArgb(192, 75, 75, 75)), x0, y0, x1, y1);
 }

 for (int i = 0; i < 10; i++)
 {
  int x0 = rand.Next(0, this.width);
  int y0 = rand.Next(0, this.height);
  int x1 = rand.Next(0, this.width);
  int y1 = rand.Next(0, this.height);

  g.DrawLine(new Pen(Color.FromArgb(192, 125, 75, 255)), x0, y0, x1, y1);
 }

Now, finally we take our Bitmap image, read it to a memory stream and convert it to a byte array so that we can simply call Convert.ToBase64String(byte[]) to output our image as a Base64 encoded string.

MemoryStream mem = new MemoryStream();
bmp.Save(mem, ImageFormat.Png);

g.Dispose();
bmp.Dispose();

return Convert.ToBase64String(mem.ToArray());
        

From here, we will get a Base64 encoded string of our CAPTCHA. Since we generated our KEY before hand, we can basically store this in a Session variable and then have it be checked against user input whenever a form submission/HTTP POST happens. We can represent the Base64 encoded string as an image in HTML as:

<img src="data:image/png;base64,iVBORw0KGgo.....eOVANkbk/wEGTdQan9X/dAAAAABJRU5ErkJggg==" />

Conclusion

So that is basically there is to it. It isn’t a bullet-proof implementation, but it gets the job done. Some of the things worth considering is that this implementation does not take user-accessibility into consideration. Since there is no support for audio output, a blind person would not be able to read and validate the CAPTCHA. I also did not take into consideration of color-blindness. So those are a few things that can be enhanced in the future.