Adding Image captcha to BlogEngine.NET

by Kevin Bosch 10. September 2010 09:42

One of the things I liked about BlogEngine.NET is that it had a hidden Captcha feature to mitigate the use of Bots posting comments on the site. However over the last couple of weeks I have noticed a vast increase in amount of bots posting comments and it is getting a little tedious deleting those comments. So I have elected to go add an image Catpcha solution on top of the BlogEngine.NET solution.

The Catpcha solution I ended up using was one derived from http://www.brainjar.com/dotNet/CaptchaImage/ however I elected to use encryption rather than session variables as an academic exercise on how to make it more scalable.

What happens is as follows:

  1. We generate a Random Text value which only contains values from ABCDEFGHJKLMNPRSTUVWXY3456789 we do this to avoid common characters eg 8 B or 0 O or 1 I because we are going to obscure the image it best to avoid those characters completely. The random test function is very simple.
    public class RandomTextGenerator : IRandomTextGenerator
        {
            /// 
            /// Settings for the random text generator.
            /// 
            public class RandomTextGeneratorSettings
            {
                public RandomTextGeneratorSettings()
                {
                    AllowedChars = "abcdefghijklmnopqrstuvxyzABCDEFGHIJLKLMNOPQRSTUVXYZ0123456789";
                    Length = 5;
                }
    
                /// 
                /// Gets or sets the length of random charachters to generate
                /// 
                /// The length.
                public int Length { get; set; }
    
    
                /// 
                /// Gets or sets the allowed chars.
                /// 
                /// The allowed chars.
                public string AllowedChars { get; set; }
            }
    
            /// 
            /// Initializes a new instance of the  class.
            /// 
            public RandomTextGenerator()
            {
                Settings = new RandomTextGeneratorSettings();
                Settings.Length = 4;
                // we take out common letters eg 8 B or 0 O or 1 I  this avoids confusion 
                Settings.AllowedChars = "ABCDEFGHJKLMNPRSTUVWXY3456789";
            }
    
            public RandomTextGenerator(RandomTextGeneratorSettings config)
            {
                Settings = config;
            }
    
    
            public RandomTextGeneratorSettings Settings { get; private set; }
    
            #region IRandomTextGenerator Members
            /// 
            /// Generate the random text.
            /// 
            /// 
            public string Generate()
            {
                Random rand = new Random(DateTime.Now.Millisecond);
                StringBuilder sb = new StringBuilder();
                int maxNumberOfChars = Settings.AllowedChars.Length - 1;
                for (int i = 0; i < Settings.Length; i++)
                {
    
                    sb.Append(Settings.AllowedChars[rand.Next(0, maxNumberOfChars)]);
                }
                return sb.ToString();
            }
            #endregion
        }
    
  2. We take the Random Text value and encrypt the value using a pre known key stored on the server and the session ID. We use a combination of private server key and public identifier to make sure the full key is a secret and specific to the session. This will avoid replay of a pre known key using the same key across different sessions will no longer work. For the Encryption I have used triple DES. For those reading very carefully you will notice I have used the session to solve this which was the very thing I was trying to avoid using.
  3. Then we take the Encrypted text and Encode it for HTML and pop that value in a hidden field.
  4. We then pass back a request to the image handler to generate the Captcha image. It will Decode the HTML then decrtypt the text using the session ID and the hidden server key. Te Text is then passed into image generator to generate an image. This image is then displayed to the user.
  5. When the user submits the form the form contains the encrypted version as well as the user entered text. The server will then decrypt the encoded text and compare the decrypted text with the users input. It all is Ok the post is accepted.
The source code for the Web Captcha is below
public class CaptchaWeb : CaptchaImageGenerator
    {
        public class CaptchaWebSettings : CaptchaImageGeneratorSettings
        {
            // stores an instance of the encryption algorithm  
            public IEncryption Crypto;
            // stores an instance of the random text generator
            public IRandomTextGenerator RandomTextGen;
            // Sets the private server key to use the encryption
            public string CryptoKey;
        }

        /// 
        /// Creates a default instance of the CaptchaWeb with all the default settings ie
        /// 
    ///
  1. uses a TripleDESCrypto algorithm
  2. ///
  3. uses a RandomTextGenerator algorithm with 4 characters and the allowed set of ACDEFGHJKLMNPRSTUVWXY345679
  4. ///
  5. Sets the server key
  6. ///
  7. defines the image width height and font
  8. ///
///
public CaptchaWeb() { Settings = new CaptchaWebSettings { Crypto = new TripleDESCrypto(), RandomTextGen = new RandomTextGenerator( new RandomTextGenerator.RandomTextGeneratorSettings {Length = 4, // we take out common letters eg 8 B or 0 O or 1 I this avoids confusion AllowedChars = "ACDEFGHJKLMNPRSTUVWXY345679"}), CryptoKey = "Pass???" , Width = 100, Height = 40, Font = "Arial" }; } public CaptchaWeb(CaptchaWebSettings captchaWebSettings) { Settings = captchaWebSettings; } /// /// A pointer to the settings used in this instance of the CaptchaWeb, if you dont like the /// defaults you can create your own and pass in via the constructior /// public new CaptchaWebSettings Settings { get; private set; } /// /// Returns a unique encryption key specific to the user session. /// /// private string GetSessionCryptoKey() { return Settings.CryptoKey + HttpContext.Current.Session.SessionID; } /// /// returns a Bitmap of the unencrypted text which has a little noise. The input is the encrypted text which we can get directly from the Url of the image. /// /// the encrypted text /// a bitmap of the clear text with a little noise public Bitmap GenerateFromCryptoText(string encodedRandomText) { string randomText = Settings.Crypto.Decrypt(encodedRandomText, GetSessionCryptoKey()); return Generate(randomText); } /// /// Gets random encrypted text /// /// public string GetRandomTextEncoded() { string text = Settings.RandomTextGen.Generate(); string encoded = Settings.Crypto.Encrypt(text, GetSessionCryptoKey()); return encoded; } /// /// Tests to see if the random encrypted text is the same as the userInput clear text. /// The Test is case insensitive /// /// the encrypted text /// the input from the user /// true or false public bool IsCorrect(string encodedText, string userInput) { string captchText = Settings.Crypto.Decrypt(encodedText, GetSessionCryptoKey()); return string.Compare(captchText, userInput, true) == 0; } }

Whilst the captcha solution works and hopefully will stop the bots in their tracks what I set out to with encryption was a little self-defeating as one of the primary goals for using encryption was to not use the session. However to avoid the replay problem I needed a transient value created by the server and known to the client the variable of choice would be the session. If you are interested in doing the same with your blog drop me a note and I will create some instructions.

Tags: , ,

C#

Comments are closed

Calendar

<<  February 2012  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
2728291234
567891011

View posts in large calendar
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012 Code Associate