Click here to Skip to main content
15,884,298 members
Articles / Web Development / ASP.NET

ASP.NET - reCAPTCHA Mailhide

Rate me:
Please Sign up or sign in to vote.
4.93/5 (10 votes)
20 Aug 2012CPOL12 min read 38.2K   683   37   10
How to use reCAPTCHA Mailhide in your ASP.NET website.

Introduction

This article describes how to use the reCAPTCHA Mailhide API to minimize spam emails within your ASP.NET website.

If your website displays the email addresses of your users or contacts as plain text, you may be increasing the amount of spam email they receive. Automated email harvesters can extract email addresses from your website pages and add them to their address lists for sending out spam emails.

The reCAPTCHA Mailhide API, provided by Google, allows your website to encrypt email addresses so that automated email harvesters can't extract them. In order to see the unencrypted email address, the user must solve a CAPTCHA by clicking on a link that opens a popup window. The unencrypted email address is then displayed to the user as a regular mailto link that can be clicked on to open a user's email program with the To: address already filled in, or it can be copied to the clipboard to be pasted into some other email program or application.

This is a sample of how the encrypted email links could appear to users. Google recommends not displaying any part of the email address if possible:

reCAPTCHA Mailhide Links

This is how the Mailhide user interface first appears to the end user when they click the email link. The link opens a popup window:

reCAPTCHA Mailhide Challenge

After solving the CAPTCHA, the user sees the unencrypted email link which is a regular HTML mailto link:

reCAPTCHA Mailhide Email Displayed

The reCAPTCHA Mailhide API is part of the reCAPTCHA web service and is discussed in other articles on CodeProject. If you are using reCAPTCHA to protect registration forms or other forms in your website from bots, using the Mailhide API will provide a consistent user interface for your website users.

Background

Google has provided several plug-ins for using reCAPTCHA in different development environments and some of these also include the Mailhide API, but the ASP.NET plug-in does not. We had a request from a client to display contact email addresses but they wanted them protected from email harvesters. I couldn't find any pre-existing implementations of the Mailhide API for ASP.NET, so I decided to implement a version that would work in that environment.

Obfuscating Email Addresses

There are several techniques for obfuscating email addresses. Some of them have been around for a while and email harvesters know how to defeat them. Other techniques make it difficult for both the email harvester and the end user to get the plain text email address.

One obfuscation technique is to spell out the email address ("johndoe at example dot com"), but that can make it difficult for the end user to enter the correct email address since they have to manually type it in or copy/paste/edit it to convert it into a proper email address. Also, many email harvesters have been designed to get around this technique.

Another technique is to use a regular mailto link with the email address in the HTML document entered in reverse order (right to left) and then use CSS to display it so that it displays left to right. However, when copied to the clipboard or clicked on, the email address is still reversed.

There are other techniques such as inserting HTML comment tags into the middle of the address, using character entities, or URL-encoding the address, but some email harvesters can get around these techniques. Even using CAPTCHA is not a guarantee that email harvesters will not be able to harvest your email addresses. If the email harvester can't process the CAPTCHA image, they could still hire people to harvest emails from websites that use CAPTCHA. In the end, you have to find the trade-off between what is the most effective in preventing or minimizing spam, what is easiest for the end user, and what is easiest to implement and maintain code-wise.

Please see the following articles on some of the different email obfuscation strategies and their success rates. Note that the third article describing success rates references another article that was written several years ago, so it may not be as applicable today.

Further Reading:

Using the Code

Use the following steps to encrypt your email addresses. More information is provided in the sections below:

  1. Obtain an encryption key from the Mailhide key generation service. This will create both a "public key" and a "private key" to use with the Mailhide service. No sign-up is required to generate the keys.
  2. Create an instance of the Mailhide class and pass the private key to it in the constructor.
  3. For each email that you want to encrypt, call the Mailhide.EncryptEmail method and pass the plain-text email address to it. This method will return the encrypted email address as a string value.
  4. Use both the public key and the encrypted email to create a link to the Mailhide service. This link can be part of any other information you choose to include such as the contact's name and title.
Obtaining an Encryption Key

You can obtain an encryption key from the Mailhide key generation service. This will create a "public key" that you will use in a link to the Mailhide service and a "private key" which you will use to encrypt the email address. No sign-up is required to generate the keys.

Since the Mailhide service uses the Advanced Encryption Standard (AES) to encrypt and decrypt the email address, the terms "public key" and "private key" are misnomers but are well suited to how the keys are used. Since AES uses symmetric encryption where one key is used to both encrypt and decrypt data, there is no true public and private key as there would be if it used asymmetric encryption.

The following snippet is from the key generation service page:

reCAPTCHA Mailhide Key Generation Service

Copy the keys and save them, preferably to web.config:

XML
<configuration>
    <appSettings>
        <add key="MailhidePublicKey" value="Your Mailhide public key" />
        <add key="MailhidePrivateKey" value="Your Mailhide private key" />
    </appSettings>
</configuration>
Using the Mailhide Class

To use the Mailhide class, create an instance of it, preferably within a using clause and pass the private key to the constructor. Then, for each email that you want to encrypt, call the EncryptEmail method and pass the plain-text email address to it. The EncryptEmail method will encrypt the email address and return it as a string value that is suitable for use in URLs.

When you have finished using the Mailhide class, call the Clear method to free up any resources that have been used and to zero out the in-memory data. This last step is recommended for all of the .NET encryption classes since garbage collection will only mark the data as being available, potentially leaving sensitive data in memory.

The following code snippet gets the public and private keys from web.config. It then loads a list of contacts using a simple Contact class that just has some public properties like FirstName, LastName, and Email. The email addresses are then encrypted and the results are displayed using an ASP.NET Repeater control.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Configuration;

public partial class _Default : System.Web.UI.Page
{
    protected string MailhidePublicKey = string.Empty;
    protected string MailhidePrivateKey = string.Empty;

    protected void Page_Load(object sender, EventArgs e)
    {
        // Get the Mailhide keys from web.config
        MailhidePublicKey = WebConfigurationManager.AppSettings["MailhidePublicKey"];
        MailhidePrivateKey = WebConfigurationManager.AppSettings["MailhidePrivateKey"];

        // Get the contacts to display
        List<Contact> contacts = GetContacts();

        // Encrypt each contact's email address
        using (Mailhide mailhide = new Mailhide(MailhidePrivateKey))
        {
            foreach (Contact contact in contacts)
            {
                contact.Email = mailhide.EncryptEmail(contact.Email);
            }
            mailhide.Clear(); // Free up resources and zero out in-memory data
        }

        // Bind the data to the Repeater control
        ContactsRepeater.DataSource = contacts;
        ContactsRepeater.DataBind();
    }

    // Contact Information
    public class Contact
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
        public string Title { get; set; }
        public string Phone { get; set; }
        public string Email { get; set; }
    }

    public List<Contact> GetContacts()
    {
        // Load some sample contacts

        List<Contact> contacts = new List<Contact>();

        contacts.Add(new Contact()
        {
            FirstName = "John",
            LastName = "Doe",
            Company = "Example Company",
            Title = "CEO",
            Phone = "222-333-4444",
            Email = "johndoe@example.com",
        });

        contacts.Add(new Contact()
        {
            FirstName = "Jane",
            LastName = "Smith",
            Company = "Example Company",
            Title = "Vice President",
            Phone = "222-333-5555",
            Email = "janesmith@example.com"
        });

        return contacts;
    }
}

The HTML markup for the Repeater control is shown below. Note the use of the public key and the email address, which is now encrypted, in the URL of the link to the Mailhide service. The public key is passed to the service using the "k" parameter of the query string value while the encrypted email address is passed using the "c" parameter.

XML
<asp:Repeater ID="ContactsRepeater" runat="server">
    <HeaderTemplate>
        <b>Contacts:</b><br />
        <br />
    </HeaderTemplate>
    <ItemTemplate>
        <%# Eval("FirstName") %>
        <%# Eval("LastName") %><br />
        <%# Eval("Title") %><br />
        <%# Eval("Company") %><br />
        <%# Eval("Phone") %><br />
        <a href='http://www.google.com/recaptcha/mailhide/d?k=<%= MailhidePublicKey %>&c=<%# Eval("Email") %>'
            title='Email this contact'>Email this contact</a>
    </ItemTemplate>
    <SeparatorTemplate>
        <br />
        <br />
    </SeparatorTemplate>
</asp:Repeater>
Using jQuery

Google recommends that the link to the Mailhide service include some JavaScript to open a popup window so that the user doesn't lose their place in the web page. If the browser does not support JavaScript or it is disabled, the Mailhide service can still be accessed through the link itself. The following code is an example:

JavaScript
<a href="http://www.google.com/recaptcha/mailhide/d?..."
  onclick="window.open('http://www.google.com/recaptcha/mailhide/d?...', '',
  'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300');
return false;" title="Reveal this e-mail address">...</a>

Using jQuery, the JavaScript used to open the popup can be removed from the anchor element and placed in a click event handler. Note that in the markup shown earlier, the anchor element does not contain any JavaScript.

The jQuery selector expression below uses an attribute selector that looks for all anchor tags with an href attribute value that contains the text "mailhide". Within the click event handler, the href attribute value is retrieved and is used to open the popup window.

Other than separating the code from the markup, which is considered a best practice, the same code can still be used for situations where you do not want to use the Mailhide service, such as when an administrator is viewing the page. In that case, you may want to display the email addresses as regular mailto links. Since the url of those links will not be pointing to the Mailhide service, the jQuery selector will not find any anchor elements to attach the click event handler to. That way, you can always include the jQuery selector expression below and know that it will only hook up the click event handler when the Mailhide service is being used.

JavaScript
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.5.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function () {
        $('a:[href*=mailhide]').click(function (event) {
            event.preventDefault();
            window.open($(this).attr('href'), '',
            'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300');
        });
    });
</script>
Using the Demo Application

To use the demo web application, generate the Mailhide keys using the Mailhide key generation service and update the corresponding values in the web.config file.

Mailhide Class Details

The following sections describe the Mailhide class in more detail, so if you are just interested in using the class, you can skip this portion.

The Mailhide class takes care of encrypting a plain-text email address and returns it as a url-safe string. The class performs some custom functionality so that the encrypted email address will be in the format that the Mailhide service expects.

Construction, Initialization, and Disposal

The Mailhide class implements the IDisposable interface so that it can call the underlying cryptographic provider's Dispose method. The constructor calls an Init method that initializes an instance of the AesManaged class, which is the cryptographic provider. Within the Init method, the cryptographic provider's Mode and IV (Initialization Vector) properties are set according to the specifications in the Mailhide API. The Padding property is set to PaddingMode.None since the Mailhide API uses a custom padding scheme. Finally, the constructor sets the PrivateKey property to the Mailhide private key that was passed in. The Mailhide private key, which is a hexadecimal string, is converted into a byte array and is then assigned to the cryptographic provider's Key property.

Note that the AesManaged class derives from the SymmetricAlgorithm class which is an abstract class that serves as the base class for all of the .NET encryption classes that implement symmetric key algorithms. An instance of the AesManaged class is created and assigned to a variable which is of the type SymmetricAlgorithm. This reinforces the idea that a symmetric algorithm is being used and allows for other SymmetricAlgorithm based classes to be used if required.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.IO;

/// <summary>
/// Helper Class for using the Google Mailhide API
/// </summary>
public class Mailhide : IDisposable
{
    protected SymmetricAlgorithm cryptoProvider = null;

    protected string privateKey = string.Empty;

    /// <summary>
    /// Initializes a new instance of the Mailhide class with the specified private key.
    /// </summary>
    /// <param name="privateKey">A 32 character hexadecimal string</param>
    public Mailhide(string privateKey)
    {
        Init();

        PrivateKey = privateKey;
    }

    /// <summary>
    /// Initialize the crypto provider
    /// </summary>
    private void Init()
    {
        cryptoProvider = new AesManaged();
        cryptoProvider.Mode = CipherMode.CBC;       // The default mode
        cryptoProvider.IV = new byte[16];           // 16 null bytes
        cryptoProvider.Padding = PaddingMode.None;  // Use custom padding in the code below
    }

    /// <summary>
    /// A 32 character hexadecimal string
    /// </summary>
    public string PrivateKey
    {
        get 
        {
            return privateKey; 
        }
        set
        {
            privateKey = value;

            // The Private Key consists of 2 digit hexadecimal characters.
            // Convert the string into a byte array
            byte[] key = new byte[privateKey.Length / 2];
            for (int i = 0; i < key.Length; i++)
            {
                key[i] = Convert.ToByte(privateKey.Substring(i * 2, 2), 16);
            }

            cryptoProvider.Key = key; // Set the encryption key
        }
    }

    // ... Other class methods and properties (including the Dispose pattern methods) ...
} 

Encryption

The EncryptEmail method performs the following three steps to encrypt the email address:

  1. Pads the email address to a fixed block size as required by AES.
  2. Encrypts the email address.
  3. Encodes the email address so that it can be used in a url query string.

Padding the Email Address

The email address string length must be some multiple of a fixed block size, which for AES, is 16 bytes. Padding characters need to be added to fill up the block if the string is too short. The padding scheme used by the Mailhide API basically involves calculating how many padding characters are needed to fill up the block and then using that number as the actual character value to use for the padding character. This can be seen in the PadString method in the source code below.

Encrypting the Email Address

The Encrypt method performs the actual encryption. The method first creates an encryptor object using the Key and IV property values from the AesManaged object. A chain of stream objects is then used with the CrypoStream object performing the actual encryption. At the end of the stream chain is a MemoryStream object that writes the encrypted data to a byte array. This byte array is then used as the return value. The source code for this method was basically pulled from the example in the AesManaged class topic on MSDN, so you can refer to that documentation for more information.

Encoding the Encrypted Email Address

In order for the encrypted email address to usable in a URL, it needs to be converted from the encrypted byte array into an encoded string. The Mailhide API documentation specifies that they use a Base-64 encoding scheme and then replace any '+' characters with a '-' and any '/' characters with a '_'. This is implemented by using the Convert.ToBase64String method and then performing the two character replacements. This encoded string, which represents the encrypted email address, is the string that should be used in the URL to the Mailhide service.

C#
/// <summary>
/// A byte array of the encrypted email address.
/// </summary>
public byte[] EncryptedData { get; protected set; }

/// <summary>
/// The encrypted and encoded url-safe email address
/// </summary>
public string EncryptedEmail { get; protected set; }

/// <summary>
/// Encrypts an email address
/// </summary>
/// <param name="emailAddress">The plain text email address</param>
/// <returns>An encrypted and encoded url-safe email address</returns>
public string EncryptEmail(string emailAddress)
{
    // Pad the email address as necessary to a 16 bit block size as required by AES
    string paddedEmailAddress = PadString(emailAddress, 16);

    // Encrypt the padded email address
    EncryptedData = Encrypt(paddedEmailAddress);

    // Encode the encrypted email address and make it url-safe.
    // This is the encrypted email that should be used in the Mailhide querystring.
    EncryptedEmail = Convert.ToBase64String(EncryptedData).Replace("+", "-").Replace("/", "_");

    return EncryptedEmail;
}

/// <summary>
/// Pads the input string to a fixed block size as required by AES.
/// </summary>
/// <param name="inputString">The string to pad</param>
/// <param name="blockSize">The block size to use. Should be 16 for AES.</param>
/// <returns>The padded string</returns>
protected string PadString(string inputString, int blockSize)
{
    string paddedString = string.Empty;

    // Pad the string to a fixed block size.
    // For example, if the block size is 16:
    // Pad a 10 character string with 6 chars (1 * blocksize)
    // Pad a 30 character string with 2 chars (2 * blocksize)
    // Use the number of characters to pad with as the padding character.

    int numToPad = blockSize - (inputString.Length % blockSize);
    string padChars = new string((char)numToPad, numToPad);
    paddedString = inputString + padChars;
    return paddedString;
}

/// <summary>
/// Encrypts the plain text string
/// </summary>
/// <param name="plainText">The string to encrypt</param>
/// <returns>The encrypted string as a byte array</returns>
protected byte[] Encrypt(string plainText)
{
    byte[] encryptedData = null;

    // Create an encryptor to perform the stream transform.
    ICryptoTransform encryptor = cryptoProvider.CreateEncryptor();

    // Create the streams used for encryption.
    using (MemoryStream memoryStream = new MemoryStream())
    {
        using (CryptoStream cryptoStream = 
                  new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
        {
            using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
            {
                // Write all data to the stream.
                streamWriter.Write(plainText);
            }

            encryptedData = memoryStream.ToArray();
        }
    }

    return encryptedData;
}

Points of Interest

The hardest part about writing the Mailhide class was trying to figure out how to set the different properties in the AesManaged class since it has a lot more properties than what is listed in the Mailhide API documentation. Should these other properties be set to specific values or use their default values? If specific values are needed, what should they be set to? Not being familiar with the .NET cryptographic classes didn't help matters either.

Another problem I had was trying to figure out how to get the private key, which is a string, into the Key property of the AesManaged class, which is a byte array. Did I need to un-encode it? Was each character in the key supposed to be an array element? In searching for some implementation details, I stumbled upon an example that was written in Python. Not knowing the Python language, I was still able to figure out from the code and from the Python language reference what I needed to do to set up the AesManaged class.

In the end, I had to experiment with several different combinations of settings to get it right. Thankfully, Google provided sample data for each step of the encryption process, from padding to encoding the encrypted data, which really helped to ensure that I was getting each step done right.

Can the Mailhide UI be Configured?

If you have used reCAPTCHA in your website application, you probably know that you can customize the look of the control using one of the predefined themes. For the Mailhide service, I couldn't find any documentation on whether you can use those same themes or not. I tried using query string values, using the JavaScript property name as the query string key, but that didn't work. So, if you don't like the fact that a popup is used to display the reCAPTCHA UI or you don't like the default colors, you will need to write your own version of the Mailhide service using the reCAPTCHA API.

History

  • April 15, 2012 - Published.
  • April 18, 2012 - Fixed typos in the article.
  • August 18, 2012 - Implement the standard Dispose pattern.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I've been an ASP.NET Web Developer for the past 8+ years, using C# and Microsoft SQL Server to develop and enhance CMS and eCommerce applications. I also occasionally develop Windows and Console utility applications.

Prior to switching to ASP.NET Web Development and C#, I developed Windows desktop applications using C and C++ for 10+ years.

Comments and Discussions

 
QuestionGood article BUT... Pin
Johnny J.29-Aug-12 0:24
professionalJohnny J.29-Aug-12 0:24 
AnswerRe: Good article BUT... Pin
Don V Adams29-Aug-12 4:25
Don V Adams29-Aug-12 4:25 
GeneralRe: Good article BUT... Pin
danito2344-Sep-12 6:06
danito2344-Sep-12 6:06 
SuggestionVery well written, I vote 5 with one suggestion Pin
danito23417-Aug-12 7:21
danito23417-Aug-12 7:21 
GeneralRe: Very well written, I vote 5 with one suggestion Pin
Don V Adams18-Aug-12 6:25
Don V Adams18-Aug-12 6:25 
GeneralRe: Very well written, I vote 5 with one suggestion Pin
danito23418-Aug-12 10:48
danito23418-Aug-12 10:48 
GeneralMy vote of 5 Pin
Monjurul Habib26-Apr-12 21:11
professionalMonjurul Habib26-Apr-12 21:11 
GeneralRe: My vote of 5 Pin
Don V Adams27-Apr-12 4:25
Don V Adams27-Apr-12 4:25 
GeneralMy vote of 5 Pin
Jani Giannoudis16-Apr-12 8:12
Jani Giannoudis16-Apr-12 8:12 
GeneralRe: My vote of 5 Pin
Don V Adams16-Apr-12 13:52
Don V Adams16-Apr-12 13:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.