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:
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:
After solving the CAPTCHA, the user sees the unencrypted email link which is a regular HTML mailto link:
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:
- 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.
- Create an instance of the
Mailhide
class and pass the private key to it in the constructor.
- 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.
- 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:
Copy the keys and save them, preferably to web.config:
<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.
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)
{
MailhidePublicKey = WebConfigurationManager.AppSettings["MailhidePublicKey"];
MailhidePrivateKey = WebConfigurationManager.AppSettings["MailhidePrivateKey"];
List<Contact> contacts = GetContacts();
using (Mailhide mailhide = new Mailhide(MailhidePrivateKey))
{
foreach (Contact contact in contacts)
{
contact.Email = mailhide.EncryptEmail(contact.Email);
}
mailhide.Clear(); }
ContactsRepeater.DataSource = contacts;
ContactsRepeater.DataBind();
}
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()
{
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.
<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:
<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.
<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.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.IO;
public class Mailhide : IDisposable
{
protected SymmetricAlgorithm cryptoProvider = null;
protected string privateKey = string.Empty;
public Mailhide(string privateKey)
{
Init();
PrivateKey = privateKey;
}
private void Init()
{
cryptoProvider = new AesManaged();
cryptoProvider.Mode = CipherMode.CBC; cryptoProvider.IV = new byte[16]; cryptoProvider.Padding = PaddingMode.None; }
public string PrivateKey
{
get
{
return privateKey;
}
set
{
privateKey = value;
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; }
}
}
Encryption
The EncryptEmail
method performs the following three steps to encrypt the email address:
- Pads the email address to a fixed block size as required by AES.
- Encrypts the email address.
- 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.
public byte[] EncryptedData { get; protected set; }
public string EncryptedEmail { get; protected set; }
public string EncryptEmail(string emailAddress)
{
string paddedEmailAddress = PadString(emailAddress, 16);
EncryptedData = Encrypt(paddedEmailAddress);
EncryptedEmail = Convert.ToBase64String(EncryptedData).Replace("+", "-").Replace("/", "_");
return EncryptedEmail;
}
protected string PadString(string inputString, int blockSize)
{
string paddedString = string.Empty;
int numToPad = blockSize - (inputString.Length % blockSize);
string padChars = new string((char)numToPad, numToPad);
paddedString = inputString + padChars;
return paddedString;
}
protected byte[] Encrypt(string plainText)
{
byte[] encryptedData = null;
ICryptoTransform encryptor = cryptoProvider.CreateEncryptor();
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream =
new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
{
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.