The new DevTips.NET website allows for people to comment on most content, unlike the old site. Obviously this opened up possibilities to add comment spam on all the places. There is a common technique to counter this, called CAPTCHA or “Completely Automated Public Turing test to tell Computers and Humans Apart”. There is also an easy ASP.NET control that you can implement on a website to implement this captcha from Miguel Jimenez. I use it on this blog. For some reason though, the captcha-image didn’t display on the new site. Several options are available: figure out what’s wrong, look for another control or… build one myself. Thinking that it shouldn’t be too hard, and being a developer, I obviously chose the last option. So here goes:
First, we need an image to display. I chose to use an HttpHandler for this. Seemed like a good choice, since we only need to Response.Write an image, and store the generated code somewhere.
<%@ WebHandler Language=“C#” Class=“HipHandler” %>
using System;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
public class HipHandler : IHttpHandler {
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = “image/jpeg”;
SendImage(context);
}
public void SendImage(HttpContext context)
{
int fontSize = 24;
int width = 100;
int height = 30;
// Setup the rectangle to draw the text on
RectangleF rectF = new RectangleF(0, 0, width, height);
Bitmap pic = new Bitmap(width, height);
Graphics g = Graphics.FromImage(pic);
g.SmoothingMode = SmoothingMode.AntiAlias;
SolidBrush fgBrush = new SolidBrush(Color.RoyalBlue);
SolidBrush bgBrush = new SolidBrush(Color.SkyBlue);
g.FillRectangle(bgBrush, rectF);
// Choose the font and style
FontStyle style = FontStyle.Regular;
Font font = new Font(“Arial”, fontSize, style, GraphicsUnit.Pixel);
// Make sure the text is centered
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
// Draw horizontal and vertical lines to make is less computer-readable
Pen p = new Pen(new SolidBrush(Color.SteelBlue), 1);
for (int i = 0; i < height; i=i+7)
{
g.DrawLine(p, 0, i, width, i);
}
for (int i = 0; i < width; i=i+10)
{
g.DrawLine(p, i, 0, i, height);
}
// Get 4 random characters, no weird ones
string randomText = GenerateRandomText(4, false);
System.Security.Cryptography.SHA256Managed hashAlg = new System.Security.Cryptography.SHA256Managed();
// Hash the text so it can be verified after postback
string hashText = HttpUtility.UrlEncode(hashAlg.ComputeHash(System.Text.Encoding.Default.GetBytes(randomText)));
context.Response.AppendCookie(new HttpCookie(“HipHash”, hashText));
// Write the text and send it to the browser
g.DrawString(randomText, font, fgBrush, rectF, format);
pic.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
public static string GenerateRandomText(int maxlength, bool ultraStrong)
{
char[] ultraChars = “!@#$%^&*~|=?{}[]qwertyuiopasdfghjklzxcvbnm1234567890!@#$%^&*~|=?{}[]”.ToCharArray();
char[] normalChars = “qwertyuiopasdfghjklzxcvbnm1234567890”.ToCharArray();
string password = string.Empty;
for (int i = 0; i < maxlength; i++)
{
char n = ‘ ‘;
if (ultraStrong) n = ultraChars[GetRandomNumber(ultraChars.Length – 1)];
if (!ultraStrong) n = normalChars[GetRandomNumber(normalChars.Length – 1)];
if (i % 2 == 0)
password += n.ToString().ToUpper();
else
password += n;
}
return password;
}
public static int GetRandomNumber(int maxvalue)
{
System.Security.Cryptography.RandomNumberGenerator random =
System.Security.Cryptography.RandomNumberGenerator.Create();
byte[] r = new byte[1];
random.GetBytes(r);
double val = (double)r[0] / byte.MaxValue;
int i = (int)Math.Round(val * maxvalue, 0);
return i;
}
public bool IsReusable {
get {
return false;
}
}
}
I’m reusing a method to generate random texts. It was originally intended to create random passwords, hence the variable names in the implementation of the method GenerateRandomText(). The randomly chosen four characters are displayed in the image and also stored in a cookie. No, not directly of course. The text is hashed making it impossible to read before it’s stored in the cookie. Other storages, like session or response headers are unavailable or unusable in an HttpHandler. Using a session to store a code is bad practice anyway.
The second part is a Web User Control that displays the image and asks the user to enter the displayed code.
<%@ Control Language=”C#” AutoEventWireup=”true” CodeFile=”UserControlHip.ascx.cs”
Inherits=”UserControlHip” %>
<img src=”HipHandler.ashx” alt=”HIP” />
Enter the code:
<asp:TextBox ID=”TextBoxHip” Width=”100″ runat=”server”></asp:TextBox>
<asp:Label ID=”LabelCodeStatus” runat=”server” Text=””></asp:Label>
Obviously you can modify the html of this user control to fit your needs and style. The code-behind is a bit more elaborate.
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class UserControlHip : System.Web.UI.UserControl
{
private string m_controlToVerify;
public string ControlToVerify
{
get { return m_controlToVerify; }
set { m_controlToVerify = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
// Verify the submitted code to the generated code
System.Security.Cryptography.SHA256Managed hashAlg = new System.Security.Cryptography.SHA256Managed();
string hashText = HttpUtility.UrlEncode(hashAlg.ComputeHash(System.Text.Encoding.Default.GetBytes(TextBoxHip.Text)));
string hashTextFromHeader = Request.Cookies[“HipHash”].Value;
LabelCodeStatus.Text = “”;
if (hashTextFromHeader != hashText)
{
// If the code is not correct, save the contents of the control to verify (usually
// a comments-textbox) in a session so visitors don’t loose their elaborate entry
// After that, redirect to the same page
TextBox textBoxControl = (TextBox)this.Parent.FindControl(m_controlToVerify);
Session[“HipControlToVerifyValue”] = textBoxControl != null ? textBoxControl.Text : “”;
Response.Redirect(Request.Url.PathAndQuery);
}
}
if (!IsPostBack)
{
TextBox textBoxControl = (TextBox)this.Parent.FindControl(m_controlToVerify);
// If return from a redirect, meaning the code entered was incorrect,
// fill the textbox with the session-stored entry.
if (Session[“HipControlToVerifyValue”] != null)
{
textBoxControl.Text = HttpUtility.HtmlEncode((string)Session[“HipControlToVerifyValue”]);
LabelCodeStatus.Text = “Wrong code”;
}
Session[“HipControlToVerifyValue”] = null;
}
}
}
The most important part of the control is to verify the code entered. Since this is stored in a cookie, we retrieve it first and then compare the hashed value of the code with the hashed value stored in the cookie. If the values match we do… nothing. Just let the rest of the page render and do any processing it may need to. However, if values do not match, there’s a Response.Redirect to the same page, so rendering and processing of the page stops.
It’s certainly a pain if you’ve just entered an elaborate comment somewhere only to make a typo when you enter the captcha-code, thereby redirecting and losing everything you wrote. So, to make it more friendly, the control has a property “ControlToVerify”. Perhaps the naming is incorrect, but this allows you to store the value of a control, before redirecting and setting the value of the control after the redirect. In this code, it has to be a textbox, but you can change it to anything you like of course.
After all this, you can put the control on any ASP.NET web page, like this:
<%@ Register Src=”UserControlHip.ascx” TagName=”UserControlHip” TagPrefix=”uc1″ %>
This Register directive is to be set at the top of the page. The following code is placed at the position you want people to enter the verification code.
<uc1:UserControlHip id=”UserControlHip1″ runat=”server” ControlToVerify=”TextBox1″></uc1:UserControlHip>
<asp:TextBox ID=”TextBox1″ runat=”server”></asp:TextBox>
<asp:Button ID=”Button1″ runat=”server” Text=”Button” />
That’s it. Please add error handling code as you see fit. The rendered image is pretty simple, and you’ve probably seen more elaborate ones. But I think it’ll do the job. If you want to see what it looks like, check out an article, code snippet or news item on DevTips.NET.
You can download a sample project that implements all this here.