ASP.NET - reCAPTCHA Mailhide






4.93/5 (10投票s)
如何在您的 ASP.NET 网站中使用 reCAPTCHA Mailhide。
引言
本文介绍如何使用 reCAPTCHA Mailhide API 来最大限度地减少 ASP.NET 网站中的垃圾邮件。
如果您的网站以明文形式显示用户或联系人的电子邮件地址,您可能会增加他们收到的垃圾邮件量。自动电子邮件采集器可以从您的网站页面中提取电子邮件地址,并将其添加到其地址列表中用于发送垃圾邮件。
Google 提供的 reCAPTCHA Mailhide API 允许您的网站加密电子邮件地址,以便自动电子邮件采集器无法提取它们。为了查看未加密的电子邮件地址,用户必须通过单击一个打开弹出窗口的链接来解决 CAPTCHA。然后,未加密的电子邮件地址将显示给用户,作为一个常规的 mailto 链接,可以单击该链接来打开用户的电子邮件程序,并将“收件人”地址预先填好,或者将其复制到剪贴板以粘贴到其他电子邮件程序或应用程序中。
这是加密电子邮件链接可能显示给用户的示例。Google 建议尽可能不要显示电子邮件地址的任何部分
当用户单击电子邮件链接时,Mailhide 用户界面首次显示给最终用户。该链接打开一个弹出窗口
解决 CAPTCHA 后,用户将看到未加密的电子邮件链接,这是一个常规的 HTML mailto 链接
reCAPTCHA Mailhide API 是 reCAPTCHA Web 服务的一部分,并在 CodeProject 的其他文章中有讨论。如果您正在使用 reCAPTCHA 来保护您网站上的注册表单或其他表单免受机器人侵害,使用 Mailhide API 将为您的网站用户提供一致的用户界面。
背景
Google 提供了几个用于在不同开发环境中使用的 reCAPTCHA 插件,其中一些插件也包含 Mailhide API,但 ASP.NET 插件不包含。我们曾收到一位客户的要求,希望显示联系人电子邮件地址,但他们希望这些地址免受电子邮件采集器的侵害。我找不到任何针对 ASP.NET 的现成 Mailhide API 实现,所以我决定实现一个在该环境中可用的版本。
混淆电子邮件地址
有几种混淆电子邮件地址的技术。其中一些技术已经存在一段时间了,电子邮件采集器知道如何破解它们。其他技术使得电子邮件采集器和最终用户都难以获取明文电子邮件地址。
一种混淆技术是拼出电子邮件地址(“johndoe at example dot com”),但这会使用户难以输入正确的电子邮件地址,因为他们必须手动输入或复制/粘贴/编辑才能将其转换为正确的电子邮件地址。此外,许多电子邮件采集器已被设计为绕过此技术。
另一种技术是使用常规的 mailto 链接,并在 HTML 文档中以相反的顺序(从右到左)输入电子邮件地址,然后使用 CSS 显示它,使其显示为从左到右。然而,当复制到剪贴板或单击时,电子邮件地址仍然是反向的。
还有其他技术,如在地址中间插入 HTML 注释标签、使用字符实体或 URL 编码地址,但一些电子邮件采集器可以绕过这些技术。即使使用 CAPTCHA 也不能保证电子邮件采集器无法抓取您的电子邮件地址。如果电子邮件采集器无法处理 CAPTCHA 图像,他们仍然可以雇人从使用 CAPTCHA 的网站上抓取电子邮件。最终,您必须在防止或最小化垃圾邮件的最有效方法、对最终用户最简单的方法以及代码实现和维护最简单的方法之间找到权衡。
请参阅以下文章,了解一些不同的电子邮件混淆策略及其成功率。请注意,第三篇关于成功率的文章引用了另一篇几年前写的文章,因此它可能不再适用。
延伸阅读
Using the Code
请使用以下步骤加密您的电子邮件地址。下面各节提供了更多信息
- 从 Mailhide 密钥生成服务获取加密密钥。这将为 Mailhide 服务创建“公钥”和“私钥”。生成密钥无需注册。
- 创建
Mailhide
类的实例,并在构造函数中传入私钥。 - 对于您要加密的每一封电子邮件,调用
Mailhide.EncryptEmail
方法,并传入明文电子邮件地址。此方法将返回加密后的电子邮件地址作为字符串值。 - 使用公钥和加密后的电子邮件创建指向 Mailhide 服务的链接。该链接可以是您选择包含的任何其他信息的一部分,例如联系人的姓名和头衔。
获取加密密钥
您可以从 Mailhide 密钥生成服务获取加密密钥。这将创建一个“公钥”,您将用它来创建指向 Mailhide 服务的链接,以及一个“私钥”,您将用它来加密电子邮件地址。生成密钥无需注册。
由于 Mailhide 服务使用高级加密标准 (AES) 来加密和解密电子邮件地址,“公钥”和“私钥”这两个术语是误称,但它们非常适合密钥的使用方式。由于 AES 使用 对称加密,即一个密钥用于加密和解密数据,因此没有真正的公钥和私钥,就像使用 非对称加密一样。
以下代码片段来自密钥生成服务页面
复制密钥并保存它们,最好是保存在 web.config 中
<configuration>
<appSettings>
<add key="MailhidePublicKey" value="Your Mailhide public key" />
<add key="MailhidePrivateKey" value="Your Mailhide private key" />
</appSettings>
</configuration>
使用 Mailhide 类
要使用 Mailhide
类,请创建一个实例,最好是在 using
子句中,并在构造函数中传入私钥。然后,对于您要加密的每一封电子邮件,调用 EncryptEmail
方法并传入明文电子邮件地址。EncryptEmail
方法将加密电子邮件地址,并将其作为适用于 URL 的字符串值返回。
完成使用 Mailhide
类后,调用 Clear
方法以释放已使用的任何资源并清零内存中的数据。对于所有 .NET 加密类,都建议执行最后一步,因为垃圾回收只会标记数据为可用,可能会将敏感数据留在内存中。
以下代码片段从 web.config 中获取公钥和私钥。然后,它使用一个简单的 Contact
类加载联系人列表,该类只有一些公共属性,如 FirstName
、LastName
和 Email
。然后加密电子邮件地址,并使用 ASP.NET Repeater
控件显示结果。
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;
}
}
上面显示了 Repeater
控件的 HTML 标记。请注意,指向 Mailhide 服务的链接的 URL 中使用了公钥和现已加密的电子邮件地址。公钥通过查询字符串值的“k”参数传递给服务,而加密的电子邮件地址通过“c”参数传递。
<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>
使用 jQuery
Google 建议,指向 Mailhide 服务的链接应包含一些 JavaScript 来打开一个弹出窗口,这样用户就不会在网页中迷失方向。如果浏览器不支持 JavaScript 或已禁用 JavaScript,仍然可以通过链接本身访问 Mailhide 服务。以下代码是一个示例
<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>
使用 jQuery,用于打开弹出窗口的 JavaScript 可以从锚元素中删除,并放置在 click 事件处理程序中。请注意,在前面显示的标记中,锚元素不包含任何 JavaScript。
下面的 jQuery 选择器表达式使用属性选择器,该选择器查找所有 href
属性值包含文本“mailhide”的锚标记。在 click 事件处理程序中,将检索 href 属性值,并用于打开弹出窗口。
除了将代码与标记分开(这被认为是最佳实践)之外,在不希望使用 Mailhide 服务的情况下(例如,当管理员查看页面时),仍然可以使用相同的代码。在这种情况下,您可能希望将电子邮件地址显示为常规的 mailto 链接。由于这些链接的 URL 不会指向 Mailhide 服务,因此 jQuery 选择器将找不到要附加 click 事件处理程序的锚元素。这样,您就可以始终包含下面的 jQuery 选择器表达式,并知道它仅在 Mailhide 服务正在使用时才会挂载 click 事件处理程序。
<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>
使用演示应用程序
要使用演示 Web 应用程序,请使用 Mailhide 密钥生成服务生成 Mailhide 密钥,并更新 web.config 文件中的相应值。
Mailhide 类详细信息
以下各节更详细地介绍了 Mailhide
类,因此,如果您只对使用该类感兴趣,可以跳过此部分。
Mailhide
类负责加密明文电子邮件地址,并将其作为 URL 安全字符串返回。该类执行一些自定义功能,以便加密的电子邮件地址符合 Mailhide 服务期望的格式。
构造、初始化和处置
Mailhide
类实现了 IDisposable
接口,以便可以调用底层加密提供程序的 Dispose
方法。构造函数调用 Init
方法,该方法初始化 AesManaged
类的实例,这是加密提供程序。在 Init
方法中,根据 Mailhide API 的规范设置加密提供程序的 Mode
和 IV
(初始化向量)属性。由于 Mailhide API 使用自定义填充方案,因此将 Padding
属性设置为 PaddingMode.None
。最后,构造函数将 PrivateKey
属性设置为传入的 Mailhide 私钥。Mailhide 私钥是一个十六进制字符串,它被转换为字节数组,然后分配给加密提供程序的 Key
属性。
请注意,AesManaged
类派生自 SymmetricAlgorithm
类,这是一个抽象类,是实现对称密钥算法的所有 .NET 加密类的基类。创建 AesManaged
类的实例并将其分配给类型为 SymmetricAlgorithm
的变量。这强化了正在使用对称算法的概念,并允许在需要时使用其他基于 SymmetricAlgorithm
的类。
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) ...
}
加密
EncryptEmail
方法执行以下三个步骤来加密电子邮件地址
- 将电子邮件地址填充到 AES 所需的固定块大小。
- 加密电子邮件地址。
- 对电子邮件地址进行编码,以便在 URL 查询字符串中使用。
填充电子邮件地址
电子邮件地址字符串的长度必须是某个固定块大小的倍数,对于 AES 来说,这个大小是 16 字节。如果字符串太短,需要添加填充字符来填充块。Mailhide API 使用的填充方案基本上涉及计算填充块所需的填充字符数量,然后使用该数量作为用于填充字符的实际字符值。这可以在下面的源代码中的 PadString
方法中看到。
加密电子邮件地址
Encrypt
方法执行实际的加密。该方法首先使用 AesManaged
对象中的 Key
和 IV
属性值创建一个加密器对象。然后使用流对象链,其中 CrypoStream
对象执行实际的加密。流链的末尾是一个 MemoryStream
对象,它将加密数据写入字节数组。然后将此字节数组用作返回值。此方法的源代码基本上是从 MSDN 上 AesManaged
类主题的示例中提取的,因此您可以参考该文档获取更多信息。
对加密的电子邮件地址进行编码
为了使加密的电子邮件地址在 URL 中可用,需要将其从加密的字节数组转换为编码字符串。Mailhide API 文档指定他们使用 Base-64 编码方案,然后用“ - ”替换任何“+”字符,用“_”替换任何“/”字符。这通过使用 Convert.ToBase64String
方法然后执行两个字符替换来实现。这个编码字符串(代表加密的电子邮件地址)应该用在 Mailhide 服务的 URL 中。
/// <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;
}
关注点
编写 Mailhide
类中最困难的部分是弄清楚如何设置 AesManaged
类中的各种属性,因为它比 Mailhide API 文档中列出的属性多得多。这些其他属性是否应设置为特定值或使用其默认值?如果需要特定值,它们应该设置为多少?不熟悉 .NET 加密类并没有让事情变得更容易。
我的另一个问题是如何将私钥(一个字符串)放入 AesManaged
类的 Key
属性中,而 Key
属性是一个字节数组。我需要对其进行解码吗?密钥中的每个字符都应该是一个数组元素吗?在搜索一些实现细节时,我偶然发现了一个用 Python 编写的示例。虽然我不知道 Python 语言,但仍然可以从代码和 Python 语言参考中弄清楚如何设置 AesManaged
类。
最终,我不得不尝试几种不同的设置组合来获得正确的结果。值得庆幸的是,Google 提供了每个加密步骤的示例数据,从填充到编码加密数据,这极大地确保了我能够正确完成每个步骤。
Mailhide UI 可以配置吗?
如果您在网站应用程序中使用过 reCAPTCHA,您可能知道可以使用预定义的主题之一来自定义控件的外观。对于 Mailhide 服务,我找不到任何关于您是否可以使用相同主题的文档。我尝试使用查询字符串值,使用 JavaScript 属性名作为查询字符串键,但这不起作用。因此,如果您不喜欢使用弹出窗口来显示 reCAPTCHA UI,或者您不喜欢默认颜色,您将需要使用 reCAPTCHA API 编写自己的 Mailhide 服务版本。
历史
- 2012 年 4 月 15 日 - 发布。
- 2012 年 4 月 18 日 - 修正文章中的拼写错误。
- 2012 年 8 月 18 日 - 实现标准的 Dispose 模式。