It Devel Criando CAPTCHA com C#
Post
Cancel

Criando CAPTCHA com C#

CAPTCHA é um acrônimo da expressão "Completely Automated Public Turing test to tell Computers and Humans Apart" (teste de Turing público completamente automatizado para diferenciação entre computadores e humanos): um teste de desafio cognitivo, utilizado como ferramenta anti-spam evitando que aplicativos automatizados realize post em formulários sem uma interação humana.

Um tipo comum de CAPTCHA requer que o usuário identifique as letras de uma imagem distorcida, às vezes com a adição de uma sequência obscurecida das letras ou dos dígitos que apareça na tela.

Tendo como base essa descrição retirada do Wikipédia (http://pt.wikipedia.org/wiki/CAPTCHA), irei demonstrar como implementar um CAPTCHA utilizando C#.

Este código gera o CAPTCHA conforme os exemplos abaixo


O Código que será utilizado foi retirado da internet em buscas pessoais, porém realizei diversos ajustes e melhorias objetivando torná-lo mais completo.

A Classe base de todo o processo foi nomeada como CaptchaImage e seu código segue abaixo

[sourcecode language="csharp"]
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;

public class CaptchaImage
{
// Public properties (all read-only).
public string Text
{
get { return this.text; }
}

public Bitmap Image
{
get { return this.image; }
}

public Byte[] PngImage
{
get
{
MemoryStream stream = new MemoryStream();
this.image.Save(stream, ImageFormat.Png);

Byte[] data = stream.ToArray();
stream = null;

return data;
}
}

public String Base64PngImage
{
get
{
return Convert.ToBase64String(PngImage);
}
}

public int Width
{
get { return this.width; }
}

public int Height
{
get { return this.height; }
}

// Internal properties.
private string text;
private int width;
private int height;
private string familyName;
private Bitmap image;

// For generating random numbers.
private Random random = new Random();

// ====================================================================
// Initializes a new instance of the CaptchaImage class using the
// specified width and height.
// ====================================================================
public CaptchaImage(Int32 length, Int32 width, Int32 height)
{
this.GenerateRandomCode(length);
this.SetDimensions(width, height);
this.GenerateImage();
}

// ====================================================================
// Initializes a new instance of the CaptchaImage class using the
// specified text, width and height.
// ====================================================================
public CaptchaImage(String s, Int32 width, Int32 height)
{
this.text = s;
this.SetDimensions(width, height);
this.GenerateImage();
}

// ====================================================================
// Initializes a new instance of the CaptchaImage class using the
// specified width, height and font family.
// ====================================================================
public CaptchaImage(Int32 length, Int32 width, Int32 height, String familyName)
{
this.GenerateRandomCode(length);
this.SetDimensions(width, height);
this.SetFamilyName(familyName);
this.GenerateImage();
}

// ====================================================================
// Initializes a new instance of the CaptchaImage class using the
// specified text, width, height and font family.
// ====================================================================
public CaptchaImage(String s, Int32 width, Int32 height, String familyName)
{
this.text = s;
this.SetDimensions(width, height);
this.SetFamilyName(familyName);
this.GenerateImage();
}

// ====================================================================
// This member overrides Object.Finalize.
// ====================================================================
~CaptchaImage()
{
Dispose(false);
}

// ====================================================================
// Releases all resources used by this object.
// ====================================================================
public void Dispose()
{
GC.SuppressFinalize(this);
this.Dispose(true);
}

// ====================================================================
// Custom Dispose method to clean up unmanaged resources.
// ====================================================================
protected virtual void Dispose(bool disposing)
{
if (disposing)
// Dispose of the bitmap.
this.image.Dispose();
}

// ====================================================================
// Generate Random Code of Captcha.
// ====================================================================
private void GenerateRandomCode(Int32 length)
{
List<String> codes = new List<String>();

for (Int32 i = 65; i <= 90; i++)
{
codes.Add(Encoding.ASCII.GetString(new Byte[] { Byte.Parse(i.ToString("X"), System.Globalization.NumberStyles.HexNumber) }));
}

/*
//Uncomment this for use Numeric text too
for (Int32 i = 0; i <= 9; i++)
{
codes.Add(i.ToString());
}*/

for (Int32 i = 97; i <= 122; i++)
{
codes.Add(Encoding.ASCII.GetString(new Byte[] { Byte.Parse(i.ToString("X"), System.Globalization.NumberStyles.HexNumber) }));
}

Random rnd = new Random();
string s = "";
for (int i = 0; i < length; i++)
s = String.Concat(s, codes[rnd.Next(0, codes.Count - 1)]);

this.text = s;
}

// ====================================================================
// Sets the image width and height.
// ====================================================================
private void SetDimensions(int width, int height)
{
// Check the width and height.
if (width <= 0)
throw new ArgumentOutOfRangeException("width", width, "Argument out of range, must be greater than zero.");
if (height <= 0)
throw new ArgumentOutOfRangeException("height", height, "Argument out of range, must be greater than zero.");
this.width = width;
this.height = height;
}

// ====================================================================
// Sets the font used for the image text.
// ====================================================================
private void SetFamilyName(string familyName)
{
// If the named font is not installed, default to a system font.
try
{
Font font = new Font(this.familyName, 12F);
this.familyName = familyName;
font.Dispose();
}
catch (Exception ex)
{
this.familyName = System.Drawing.FontFamily.GenericSerif.Name;
}
}

// ====================================================================
// Creates the bitmap image.
// ====================================================================
private void GenerateImage()
{
// Create a new 32-bit bitmap image.
Bitmap bitmap = new Bitmap(this.width, this.height, PixelFormat.Format32bppArgb);

Color backColor = Color.FromArgb((random.Next(100, 255)),
(random.Next(100, 255)), (random.Next(100, 255)));

Color foreColor = Color.FromArgb(random.Next(0, 100),
random.Next(0, 100), random.Next(0, 100));

// Create a graphics object for drawing.
Graphics g = Graphics.FromImage(bitmap);
g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = new Rectangle(0, 0, this.width, this.height);

HatchStyle[] aHatchStyles = new HatchStyle[]
{
HatchStyle.BackwardDiagonal, HatchStyle.Cross,
HatchStyle.DashedDownwardDiagonal, HatchStyle.DashedHorizontal,
HatchStyle.DashedUpwardDiagonal, HatchStyle.DashedVertical,
HatchStyle.DiagonalBrick, HatchStyle.DiagonalCross,
HatchStyle.Divot, HatchStyle.DottedDiamond, HatchStyle.DottedGrid,
HatchStyle.ForwardDiagonal, HatchStyle.Horizontal,
HatchStyle.HorizontalBrick, HatchStyle.LargeCheckerBoard,
HatchStyle.LargeConfetti, HatchStyle.LargeGrid,
HatchStyle.LightDownwardDiagonal, HatchStyle.LightHorizontal,
HatchStyle.LightUpwardDiagonal, HatchStyle.LightVertical,
HatchStyle.Max, HatchStyle.Min, HatchStyle.NarrowHorizontal,
HatchStyle.NarrowVertical, HatchStyle.OutlinedDiamond,
HatchStyle.Plaid, HatchStyle.Shingle, HatchStyle.SmallCheckerBoard,
HatchStyle.SmallConfetti, HatchStyle.SmallGrid,
HatchStyle.SolidDiamond, HatchStyle.Sphere, HatchStyle.Trellis,
HatchStyle.Vertical, HatchStyle.Wave, HatchStyle.Weave,
HatchStyle.WideDownwardDiagonal, HatchStyle.WideUpwardDiagonal, HatchStyle.ZigZag
};

// Fill in the background.
HatchBrush hatchBrush = new HatchBrush(aHatchStyles[random.Next
(aHatchStyles.Length - 1)], backColor, Color.White);
g.FillRectangle(hatchBrush, rect);

// Set up the text font.
SizeF size;
float fontSize = rect.Height * 2;
Font font;

// Adjust the font size until the text fits within the image.
do
{
fontSize -= 0.3F;
font = new Font(this.familyName, fontSize, FontStyle.Bold);
size = g.MeasureString(this.text, font);
} while ((size.Width > rect.Width) || (size.Height > rect.Height));

fontSize = fontSize + (fontSize * 0.20F);
font = new Font(this.familyName, fontSize, FontStyle.Bold);

// Set up the text format.
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;

// Create a path using the text and warp it randomly.
GraphicsPath path = new GraphicsPath();
path.AddString(this.text, font.FontFamily, (int)font.Style, font.Size, rect, format);

float v = 20F;
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(rect.Width - this.random.Next(rect.Width) / v, rect.Height - this.random.Next(rect.Height) / v)
};

Matrix matrix = new Matrix();
matrix.Translate(0F, 0F);
path.Warp(points, rect, matrix, WarpMode.Perspective, 0F);

// Draw the text.
hatchBrush = new HatchBrush(hatchBrush.HatchStyle, foreColor, backColor);
g.FillPath(hatchBrush, path);

// Add some 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(hatchBrush, x, y, w, h);
}

// Draw random lines
Int32 linesCount = random.Next(3, 5);
Pen lPen = new Pen(new SolidBrush(hatchBrush.ForegroundColor));
for (Int32 l = 1; l <= linesCount; l++)
{
g.DrawLine(lPen,
new Point(random.Next(0, this.width), random.Next(0, this.height)),
new Point(random.Next(0, this.width), random.Next(0, this.height))
);
}

// Clean up.
font.Dispose();
hatchBrush.Dispose();
g.Dispose();

// Set the image.
this.image = bitmap;
}
}

[/sourcecode]

Para sua utilização há, pelo menos, duas metodologias possíveis, uma página que retornará uma imagem, ou uma tag <img> com o base64 da imagem.

Segue o código da página que retorna a imagem

[sourcecode language="csharp"]
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.IO;
using System.Drawing;

public partial class EventImage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
try
{
//Set Response code
this.Response.StatusCode = 200;
this.Response.Status = "200 OK";

//Add last modified date
this.Response.AddHeader("Last-Modified", DateTime.Now.ToString("r", System.Globalization.CultureInfo.CreateSpecificCulture("en-US")));

//Change content type
this.Response.ContentType = "image/png";

//Create the captcha bitmap
CaptchaImage cap = new CaptchaImage(6, 130, 40, "Verdana");

//Get Byte array of image in PNG
Byte[] imgData = cap.PngImage;

//Set the byte array of the image in output stream
this.Response.OutputStream.Write(imgData, 0, imgData.Length);

//Set the session of the text for the captcha validation
Session["captchaText"] = cap.Text;

}
catch (Exception ex)
{
//Set error response code
this.Response.Status = "505 Internal error";
this.Response.StatusCode = 505;
}

}
}

[/sourcecode]

Segue o código para retorno do base64 da imagem

[sourcecode language="csharp"]
//Create the captcha bitmap
CaptchaImage cap = new CaptchaImage(6, 130, 40, "Verdana");

Holder1.Controls.Add(new LiteralControl("<img border=\"0\" style=\"width: 200px; height: 50px; background: url('data:image/png;base64," + cap.Base64PngImage + "') no-repeat scroll 0px 0px transparent;\" src=\"/images/empty.gif\" title=\"Imagem de confirmação\">"));

//Set the session of the text for the captcha validation
Session["captchaText"] = cap.Text;
[/sourcecode]

Note que em ambos as metodologias acima, foi gravado em uma sessão o texto do captcha, desta fora a validação pode ser realizada com esta sessão conforme exemplo abaixo

[sourcecode language="csharp"]
String captchaText = Request.Form["captcha"];
String sCaptchaText = (String)Session["captchaText"];
if (sCaptchaText == null)
sCaptchaText = "";

if (captchaText.ToLower() = sCaptchaText.ToLower())
{
//OK
}
[/sourcecode]