Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Captcha for aspx using VB.NET

0.00/5 (No votes)
4 Jan 2011 2  
Produce a captcha image for an aspx web page

Introduction

To reduce the incidence of spam and reduce the likelihood of denial of service attacks, many web pages include an image block with distorted text, known as a Captcha. (There is a good article on Wikipedia at http://en.wikipedia.org/wiki/CAPTCHA.)

The Code Project and other open sources have a number of code snippets for producing a captcha image on a web page. However, the majority are old and use moderately complex JavaScript and/or C#. The code in this article using Visual Basic and the increasing power available in the .NET Framework to produce significantly more complex and simple code. For example, the 30 lines needed in the past to convert binary data to base64 can now be done in a single line.

The image is drawn by obtaining a graphics object from the in memory bit map. Again the .NET framework has simplified the process of transforming the bitmap to a form that can be displayed on a webpage with the bitmap.save(stream, imageformat) function to write the image to a memory stream. The memory stream is then read and converted to the required base64 string.

The captcha is not particularly sophisticated. However, it would be easy to modify the code to have, for example, smoother angle and size changes and have a more (human) attractive disruptive line across the text.

Using the Code

The code is a short class which has a method for generating a fake word and a second method for producing an in-memory bitmap with the distorted text. Having produced these, the fake word is place in a hidden text box on the page and the image is added to the page by modifying the src attribute of an <img /> tag placed on the page.

(I wish to acknowledge www.tipstricks.org for the basis of the fake word generation - written in script but now translated into VB.NET. The RndInterval function could have been updated, but it was not worth doing so).

Imports System.Drawing
Public Class Captcha
Dim bmpCaptcha As Bitmap
Dim iBMPHeight As Integer = 50
Dim iBMPWidth As Integer = 220
Dim sLeftMargin As Single = 20
Dim sTopMargin As Single = 10
Dim g As Graphics
Dim sWord As String
Dim sLetter As String
Dim sfLetter As SizeF
Dim rfLetter As RectangleF
Dim sX1 As Single = 0
Dim sY1 As Single = 0
Dim sX2 As Single = 0
Dim sY2 As Single = 0
Dim sTemp As Single
Dim iAngle As Integer
Dim sOffset As Single

Public Function GenerateCaptcha(ByVal sWord As String) As Bitmap
Dim ixr As Integer
bmpCaptcha = New Bitmap(iBMPWidth, iBMPHeight, _
		Drawing.Imaging.PixelFormat.Format16bppRgb555)
g = Graphics.FromImage(bmpCaptcha)
Dim drawBackground As New SolidBrush(Color.Silver)
g.FillRectangle(drawBackground, New Rectangle(0, 0, iBMPWidth, iBMPHeight))

' Create font and brush.
Dim drawFont As New Font("Times New Roman", 20)
Dim drawBrush As New SolidBrush(Color.DarkBlue)
Dim strFormat As New StringFormat(StringFormatFlags.FitBlackBox)
sfLetter = New SizeF(30, 30)
' Draw string to screen.
For ixr = 0 To sWord.Length - 1
sLetter = sWord.Substring(ixr, 1)
g = Graphics.FromImage(bmpCaptcha)
rfLetter = New RectangleF(sLeftMargin, sTopMargin, 30, 30)
sfLetter = g.MeasureString(sLetter, drawFont, sfLetter, strFormat)
iAngle = RndInterval(0, 20) - 10
With rfLetter
sOffset = sLeftMargin * Math.Tan(iAngle * Math.PI / 180)
.Y = sTopMargin - sOffset
.Width = sfLetter.Width + 10
.Height = sfLetter.Height
End With
g.RotateTransform(iAngle)
g.DrawString(sLetter, drawFont, drawBrush, rfLetter)
sLeftMargin += sfLetter.Width + 2
Next
Dim drawPen As Pen = New Pen(Color.Crimson, 1)
For ixr = 0 To 3
sX1 = sX2
Do While Math.Abs(sX1 - sX2) < iBMPWidth * 0.5
sX1 = RndInterval(2, iBMPWidth - 2)
sX2 = RndInterval(2, iBMPWidth - 2)
Loop
sY1 = sY2
Do While Math.Abs(sY1 - sY2) < iBMPHeight * 0.5
sY1 = RndInterval(2, iBMPHeight - 2)
sY2 = RndInterval(2, iBMPHeight - 2)
Loop
If RndInterval(0, 2) > 1 Then
sTemp = sX1
sX1 = sX2
sX2 = sTemp
End If
If RndInterval(0, 2) > 1 Then
sTemp = sY1
sY1 = sY2
sY2 = sTemp
End If

g.DrawLine(drawPen, sX1, sY1, sX2, sY2)
Next
g.Dispose()
Return bmpCaptcha
End Function
Public Function GenerateFakeWord(ByVal iLengthRequired As Integer) As String
Dim sVowels As String = "AEIOU"
Dim sConsonants As String = "BCDFGHJKLMNPQRSTVWXYZ"
Dim iNbrVowels As Integer
Dim iNbrConsonants As Integer
Dim sWord As String
Dim bUseVowel As Boolean
Dim iWordLength As Integer
Dim sPattern As String
iNbrVowels = 0
iNbrConsonants = 0
bUseVowel = False
sWord = ""
Randomize()
For iWordLength = 1 To iLengthRequired
If (iWordLength = 2) Or ((iLengthRequired > 1) And (iWordLength = iLengthRequired)) Then
bUseVowel = Not bUseVowel
ElseIf (iNbrVowels < 2) And (iNbrConsonants < 2) Then
bUseVowel = ((Rnd(1) * 2) > 1)
ElseIf (iNbrVowels < 2) Then
bUseVowel = True
ElseIf (iNbrConsonants < 2) Then
bUseVowel = False
End If

sPattern = IIf(bUseVowel, sVowels, sConsonants)
sWord = sWord & sPattern.Substring(Int(Rnd(1) * sPattern.Length), 1)
If bUseVowel Then
iNbrVowels = iNbrVowels + 1
iNbrConsonants = 0
Else
iNbrVowels = 0
iNbrConsonants = iNbrConsonants + 1
End If
Next
Return sWord
End Function
Private Function RndInterval(ByVal iMin As Integer, ByVal iMax As Integer) As Integer
Randomize()
Return Int(((iMax - iMin + 1) * Rnd()) + iMin)
End Function
End Class

To use class, include something like the following three tags on the aspx page:

<body onload="onload()">

<asp:Image ID="imgCaptcha" runat="server" style="z-index: 1; left: 570px; top: 220px; 
position: absolute; width: 220px; height: 50px;" /> 

<asp:TextBox ID="txtCaptcha" runat="server" 
style="z-index: 1; left: 67px; top: 474px; position: absolute" 
Visible="False">txtCaptcha</asp:TextBox>

And, finally, you will need some code in the aspx.vb code page to generate the captcha image and display it on the web page. The variable bOutputCaptcha can be set to output the captcha image or have the page OnLoad function do nothing and hide the image control. Typically, the page will be reloaded after the user's response which can be compared with the word which is stored in the hidden textbox, txtCaptcha. (The viewstate of aspx controls is said to be sufficiently secure that it is unlikely a computer can extract the value easily. However, the value could be left server side if you really really thought this necessary).

Private Sub ForgottenPassword_PreRender_
	(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender

        ' Example of inline base64 image tag: <img width="16" height="14" 
        ' alt="embedded folder icon" src="data:image/gif;base64,
        ' R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//
        ' mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcpp
        ' V0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAg
        ' VGoEGv2xsBxqNgYPj/gAwXEQA7" />

        Dim sWord As String
        Dim bmpCaptcha As Bitmap
        Dim sScript As String
        Dim sImage64 As String
        Dim iLength As Integer
        Dim iRead As Integer
        Dim msCaptcha As MemoryStream

        If bOutputCaptcha Then

              sWord = myCaptcha.GenerateFakeWord(6)
              txtCaptcha.Text = sWord

              bmpCaptcha = myCaptcha.GenerateCaptcha(sWord)

              '   The following commented out code can be used for testing
              '   It writes the image to a page by itself
             'Response.ContentType = "image/jpeg"
             'Response.AppendHeader("Content-Disposition", "inline; filename=captcha.bmp")
             'Response.CacheControl = "no-cache"
             'Response.AppendHeader("Pragma", "no-cache")
             'Response.Expires = -1
             'bmpCaptcha.Save(Response.OutputStream, Imaging.ImageFormat.Jpeg)
             'Response.Flush()

             msCaptcha = New MemoryStream
             bmpCaptcha.Save(msCaptcha, Imaging.ImageFormat.Jpeg)

             sImage64 = ""

             iLength = msCaptcha.Length
             Dim byteImage As Byte()

             byteImage = New Byte(CType(msCaptcha.Length, Integer)) {}

             msCaptcha.Position = 0
             iRead = msCaptcha.Read(byteImage, 0, iLength)
             sImage64 = Convert.ToBase64String(byteImage)

             sScript = vbCr + <script language="Javascript"> " vbCr

             sScript += "function onload() {" + vbCr
             sScript += "var image;" + vbCr
             sScript += "image=document.getElementById(""imgCaptcha"");" + vbCr
             sScript += "image.src='data:image/gif;base64," + sImage64 + "'"
            
             '   It would be possible to test for IE7 or earlier here and 
             '   put the captcha word into the alt text - but 
             '   does increase the chances of a denial of service attack 
             '   if someone figures this out
             sScript += "}" + vbCr + vbCr
             sScript+="</Script>" + vbCr

             Me.ClientScript.RegisterClientScriptBlock(Me.GetType(), quot;image", sScript)

             msCaptcha.Close()
             bmpCaptcha.Dispose()

        Else

             imgCaptcha.Visible = False
             sScript = vbCr + <script language="Javascript"> " vbCr
             sScript += "function onload() {" + vbCr
             sScript += "}" + vbCr + vbCr
             sScript+="</Script>" + vbCr

             Me.ClientScript.RegisterClientScriptBlock(Me.GetType(), "image", sScript)

        End If

    End Sub

Note the little bit of code that is commented out - it is possible to save the bitmap directly to the http response stream and display the image alone on the page for testing purposes.

The user will need to have scripting permitted on their browser.

The image uses the inline URI for image display. The image is encoded in base64 (i.e. 7 bit characters). This means the image is typically 30% larger than the original bit map (which in this example is typically about 3KB).

Not all browsers will display inline base64 images - most importantly Internet Explorer 7 and earlier will not display them. Internet Explorer 8 and later will display the images and have a limited of 32KB. Firefox and Safari are said to have done so for much longer, but many are limited to 4KB images. The bitmap images produced here are typically 3KB so that even after the conversion to base64 should be able to be displayed.

(I hope the script is accurately translated. The CodeProject web page thought it was malicious code and removed everything between the <script></script> tags - so I could not copy it directly and had to type the code between separately!!)

History

  • No changes have been made to the code at this stage (Jan 2011).

' http://www.tipstricks.org/

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here