Pages

Friday, March 30, 2012

My Captcha Image

This is a work in progress, and for my furthering my notes. Your comments are welcome.

I decide to code my personal Captcha Image. My Captcha idea is new, which probably can be found somewhere on the internet, however I tweaked the idea into a .NET Class (CaptchaDisplay class within my Captcha namespace)..

This Captcha class is instantiated from two areas. It is instantiated within an User control and a Generic Handler.

The User control’s code behind handles generating the Captcha characters, while the Generic Handler handles creating the Image from characters passed it through a query string. The characters are generated from the User Control.

I added an external appSettings page to the Web.config:

<appSettings file="appSettings.config"/>

Within the appSettings.config file:

<?xml version="1.0"?>
<appSettings>
<add key="Tumblers" value="j(mW*)UYRvxQ$4^54hT" />
</appSettings>
 
The key attribute – Tumblers - is used in the Captcha encryption. Tumblers is synonymous with a Key Lock mechanism. Tumblers wouldn’t be shown and are invisible. The Lock and Key can be found by the visitor, however to unlock or to be verified all three must fit or equal to the Captcha characters generated.

Download myCaptcha code here

The User Control is shown below:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="CaptchaControl.ascx.cs"
    Inherits="CaptchaHandler.CaptchaControl" %>
 
<asp:HiddenField ID="captchaKey" ClientIDMode="Static" runat="server" />
<asp:Panel ID="Captcha" ClientIDMode="Static" runat="server">
</asp:Panel>

and its Code Behind page:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using deDogs.Captcha;
 
namespace CaptchaHandler
{
    public partial class CaptchaControl : System.Web.UI.UserControl
    {
        private int _width;
 
        public int Width
        {
            set
            {
                _width = value;
            }
        }
 
        protected void Page_Load(object sender, EventArgs e)
        {
            using (CaptchaDisplay captcha = new CaptchaDisplay(_width))
            {
                captcha.GetCharacters();
                string[] c = captcha.characters;
                string key =new string(captcha.SaltKey(c));
                string encodeKey =  HttpUtility.UrlEncode(key);
                captchaKey.Value = encodeKey;
 
                Image img = new Image();
                img.ImageUrl = "Captcha.ashx?key=" + encodeKey + "&w=" + _width;
                img.AlternateText = "Captcha Image";
                Captcha.Controls.Add(img);
            }
        }
    }
}
Generic Handler C# Code is shown below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using deDogs.Captcha;
using System.Drawing.Imaging;
 
namespace CaptchaHandler
{
    /// <summary>
    /// Display Captcha Characters Image
    /// </summary>
    public class CaptchaHandler : IHttpHandler
    {
 
        public void ProcessRequest(HttpContext context)
        {
            HttpContext.Current.Response.ContentType = "image/jpeg";
            HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            HttpContext.Current.Response.BufferOutput = false;
            try
            {
                Int32 width = Convert.ToInt32(HttpContext.Current.Request["w"]);
 
                if (width > 0) {
                    using (CaptchaDisplay captcha = new CaptchaDisplay(width))
                    {
                        try
                        {
                            string key = HttpUtility.UrlDecode(HttpContext.Current.Request["key"].ToString());
 
                            if (key != "")
                            {
                                //Decrypt key
                                char[] unSalted = captcha.SaltKey(key.ToCharArray());
                                captcha.characters = unSalted.SelectMany(x => new string[] { x.ToString() }).ToArray();
                                captcha.CaptchaImage();
                                captcha._bitMap.Save(HttpContext.Current.Response.OutputStream, ImageFormat.Jpeg);
                            }
                        }
                        catch (Exception)
                        {
 
                            throw;
                        }
                    }
                }
            }
            catch (Exception)
            {
                
                throw;
            }
 
        }
 
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

Finally the Captch class shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web;
using deDogs.Validation;
using System.Configuration;
using System.Web.SessionState;
namespace deDogs
{
    namespace Captcha
    {
        public class CaptchaDisplay : IDisposable
        {
            public CaptchaDisplay(int width)
            {
                this.fonts = new string[] { "Times New Roman", "Georgia", "Arial", "Comic Sans MS" };
                this.sizes = new int[] { 36, 48, 60, 72 };
                this.characterSet = @"ABDEFGHJKLMNQRTYZabdefghkmnqryz23456789*#@%$";
                this.colr = new Brush[] { Brushes.Black };
                this.backgroundColor = Brushes.White;
                this.salt = new char[] { 'g', '-', '$', 'k', '5', '?', 'O' };
                this._bitMap = new Bitmap(width, (int)(width * .32F));
            }
            public CaptchaDisplay() { }
 
            protected class CaptchaChars
            {
                public CaptchaChars() { }
 
                public CaptchaChars(string character, CaptchaRegion region)
                {
                    this.character = character;
                    this.region = region;
                    this.memoryStream = new MemoryStream();
 
                }
                public MemoryStream memoryStream { get; set; }
                public string character;
                public CaptchaRegion region;
                public int angle { get; set; }
            }
            protected class CaptchaRegion
            {
                public CaptchaRegion() { }
                public CaptchaRegion(float x, float y, float width, float height)
                {
                    this.x = x;
                    this.y = y;
                    this.width = width;
                    this.height = height;
                }
                public float x { get; set; }
                public float y { get; set; }
                public float width { get; set; }
                public float height { get; set; }
            }
            public Bitmap _bitMap;
 
            Brush color(Random rnd)
            {
                return colr[rnd.Next(0, colr.Length)];
            }
            PinTumblerLock pinTumblerLock;
 
            public char[] salt { get; private set; }
            public string[] characters;
            public string characterSet;
 
            public string[] fonts { get; set; }
            public int[] sizes { get; set; }
            public Brush[] colr;
            public Brush backgroundColor;
 
            public PinTumblerLock LockKey
            {
                get
                {
                    return pinTumblerLock;
                }
                set
                {
                    pinTumblerLock = value;
                }
            }
            public void GetCharacters()
            {
                Random rnd = new Random();
                int numChars = rnd.Next(4, 7);
                this.characters = new string[numChars];
 
                for (var i = 0; i < numChars; i++)
                {
                    this.characters[i] = this.characterSet.Substring(rnd.Next(0, this.characterSet.Length), 1);
                }
            }
 
            public void CaptchaImage()
            {
                Random rnd = new Random();
 
                CaptchaChars captchaChar;
                List<CaptchaChars> captchaChars = new List<CaptchaChars>();
 
                CharacterRange[] cR = { new CharacterRange(0, 1) };
 
                StringFormatFlags flags = StringFormatFlags.NoWrap | StringFormatFlags.NoClip;
                StringFormat sF = new StringFormat(flags);
                sF.SetMeasurableCharacterRanges(cR);
 
                RectangleF displayRectangleF = new RectangleF(0, 0, this._bitMap.Width, this._bitMap.Height);
 
                Font largeFont;
 
                int width = 80, height = 80;
 
                for (var c = 0; c < characters.Length; c++)
                {
                    HttpContext.Current.Trace.Write(characters[c]);
 
                    int angle = rnd.Next(0, 30);
                    angle = (angle % 2 == 0) ? -angle : angle;
 
                    captchaChar = new CaptchaChars(characters[c], new CaptchaRegion(0, 0, width, height));
                    captchaChar.angle = angle;
 
                    using (Bitmap bitMap = new Bitmap(width, height, PixelFormat.Format32bppArgb))
                    {
                        using (Graphics g = Graphics.FromImage(bitMap))
                        {
                            Rectangle rect = new Rectangle(new Point(0, 0), new Size(bitMap.Width, bitMap.Height));
                            g.FillRectangle(this.backgroundColor, rect);
 
                            largeFont = new Font(this.fonts[rnd.Next(0, this.fonts.Length)], this.sizes[rnd.Next(0, this.sizes.Length)], GraphicsUnit.Pixel);
                            Region[] charRegion = g.MeasureCharacterRanges(characters[c], largeFont, displayRectangleF, sF);
                            g.DrawString(characters[c], largeFont, colr[rnd.Next(0, colr.Length)], displayRectangleF);
 
                            bitMap.Save(captchaChar.memoryStream, ImageFormat.Jpeg);
                            captchaChars.Add(captchaChar);
                        }
                    }
                }
 
 
                this.Display(captchaChars);
 
            }
 
            void Display(List<CaptchaChars> c)
            {
                using (Graphics mainGraphic = Graphics.FromImage(this._bitMap))
                {
                    StringBuilder charSet = new StringBuilder();
                    Rectangle rect = new Rectangle(new Point(0, 0), new Size(this._bitMap.Width, this._bitMap.Height));
                    mainGraphic.FillRectangle(this.backgroundColor, rect);
 
                    int newX = 0;
                    float bwidth, bheight = 0;
                    foreach (CaptchaChars m in c)
                    {
                        bwidth = m.region.width;
                        bheight = m.region.height;
                        Bitmap bitMap = new Bitmap(m.memoryStream);
                        using (Graphics g = Graphics.FromImage(bitMap))
                        {
                            Rotate(m, g);
                            g.DrawImage(bitMap, 0, 0);
                            mainGraphic.DrawImage(bitMap, newX, 0);
                            newX += (int)m.region.width;
                            charSet.Append(m.character);
                        }
 
                    }
                    //Crop image bounding selected characters
                    this._bitMap = this._bitMap.Clone(new Rectangle(0, 0, newX, (int)bheight), PixelFormat.Format32bppArgb);
                    Lockup(charSet.ToString());
 
                    Distort();
                    Decorate();
                }
            }
 
            private void Lockup(string key)
            {
                PinTumblerLock lockup = new PinTumblerLock(key, (ConfigurationManager.AppSettings["Tumblers"]));
            }
 
            public char[] SaltKey(char[] key)
            {
                int len = key.Length;
 
                for (int i = 0; i < len; i++)
                {
                    key[i] ^= this.salt[i];
                }
                return key;
 
            }
            public char[] SaltKey(string[] key)
            {
                char[] c = string.Join(string.Empty, key).ToCharArray();
                return this.SaltKey(c);
            }
 
            void Rotate(CaptchaChars c, Graphics g)
            {
                g.TranslateTransform(c.region.width / 2, c.region.height / 2);
                g.RotateTransform(c.angle);
                g.TranslateTransform(-c.region.width / 2, -c.region.height / 2);
            }
 
            protected void Distort()
            {
                Random rnd = new Random();
                double distort = rnd.Next(5, 20) * (rnd.Next(10) == 1 ? 1 : -1);
 
                int width = this._bitMap.Width;
                int height = this._bitMap.Height;
                using (Bitmap copy = (Bitmap)this._bitMap.Clone())
                {
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            // Adds a simple wave
                            int newX = (int)(x + (distort * Math.Sin(Math.PI * y / 84.0)));
                            int newY = (int)(y + (distort * Math.Cos(Math.PI * x / 44.0)));
                            if (newX < 0 || newX >= width) newX = 0;
                            if (newY < 0 || newY >= height) newY = 0;
                            this._bitMap.SetPixel(x, y, copy.GetPixel(newX, newY));
                        }
                    }
                }
            }
            protected void Decorate()
            {
                using (Graphics g = Graphics.FromImage(this._bitMap))
                {
                    Random rnd = new Random();
                    int width = this._bitMap.Width;
                    int height = this._bitMap.Height;
 
                    // Draw lines between original points to screen.
                    g.DrawCurve(new Pen(color(rnd), 3.0F), Points(5, rnd).ToArray());
 
                    foreach (Point pt in Points(5, rnd))
                    {
                        g.DrawRectangle(new Pen(color(rnd), 3.0F), new Rectangle(pt, new Size(rnd.Next(5, 20), rnd.Next(5, 20))));
                    }
                }
            }
 
            protected List<Point> Points(int pts, Random rnd)
            {
                List<Point> points = new List<Point>();
                int width = this._bitMap.Width - 20;
                int height = this._bitMap.Height - 20;
 
                for (int i = 0; i < pts; i++)
                {
                    points.Add(new Point(rnd.Next(0, width), rnd.Next(0, height)));
                }
                return points;
            }
 
            public void Dispose()
            {
                this._bitMap.Dispose();
                this._bitMap = null;
            }
        }
    }
}