在 C# 和 MSSQL 2000 中使用 MD5 加密






3.58/5 (10投票s)
2006年1月4日
4分钟阅读

104936
演示在 MSSQL 2000 中创建和验证 MD5 加密密码的方法。
引言
本文将演示如何使用 MD5 加密来读取和写入 MSSQL 2000 数据库中的密码信息。
背景
“.NET Framework 为开发人员提供了一些易于使用的类来进行现代加密。如今,MD5 加密是更受欢迎的方法之一。MD5 加密,引用 RFC 1232 的话来说,“接受任意长度的消息作为输入,并产生一个 128 位‘指纹’或‘消息摘要’作为输出。据推测,要想产生两条具有相同消息摘要的消息,或者产生一条具有给定预定目标消息摘要的消息,在计算上是不可行的。MD5 算法用于数字签名应用程序,其中大型文件必须以安全的方式‘压缩’,然后才能使用像 RSA 这样的公钥密码系统下的私钥(秘密密钥)进行加密。” 它由麻省理工学院的 Ronald L. Rivest 教授开发,并已广泛用作 ASP.NET 应用程序的标准加密方法。有关 MD5 使用的更多实用信息,请参阅本文底部的“趣味点”。
入门
由于 .NET 使 MD5 加密变得如此易于使用,因此我没有包含演示项目。我将只包含必需的 C# 方法和一个用于创建测试数据库表的 SQL 脚本。创建测试表
使用您的 MSSQL 2000 本地实例(这可能也适用于 2005,但我尚未测试)。您可以随意重命名表和列,只需确保更改我将在下面详细介绍的方法调用。现在,只需从您拥有的数据库中运行以下 SQL 脚本。
if exists (select * from dbo.sysobjects where
id = object_id(N'[dbo].[tblLogins]')
and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[tblLogins]
GO
CREATE TABLE [dbo].[tblLogins] (
[Login] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[Password] [binary] (16) NULL
) ON [PRIMARY]
GO
我们在这里所做的只是创建一个名为 'tblLogins' 的表,其中包含两列:一个登录列(varchar 25)和一个密码列(binary 16)。
添加新登录
您可以在您的 ASP.NET Web Form 或 C# Windows Form 应用程序中使用以下方法。我们所做的只是连接到数据库并插入新的登录。我创建了一个名为 ValidationCode 的枚举,以便能够更清晰地处理方法的响应,但您也可以使用 void 函数达到相同效果。
/* Return types that are thrown when login is attempted */
public enum ValidationCode
{
LoginFailed=1,
LoginSucceeded=2,
ConnectionFailed=3,
UnspecifiedFailure=4,
LoginCreated=5
}
您需要确保已将以下引用添加到您的应用程序中。
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Text;
CreateNewLogin 将接受 5 个值。第一个值是数据库中存储登录信息的表名(在此示例中为 tblLogin),第二个和第三个值是我们期望的登录名和密码(明文),最后两个值分别是表中登录列(Login)和密码列(Password)的名称。
public ValidationCode CreateNewLogin(string tableName, string strLogin,
string strPassword, string loginColumn, string passColumn)
{
//Create a connection
string strConnString = System.Configuration.ConfigurationSettings.
AppSettings["ConnString"];
SqlConnection objConn = new SqlConnection(strConnString);
// Create a command object for the query
string strSQL = "INSERT INTO " + tableName + " (" + loginColumn +
"," + passColumn + ") " + "VALUES(@Username, @Password)";
SqlCommand objCmd = new SqlCommand(strSQL, objConn);
//Create parameters
SqlParameter paramUsername;
paramUsername = new SqlParameter("@Username", SqlDbType.VarChar, 10);
paramUsername.Value = strLogin;
objCmd.Parameters.Add(paramUsername);
//Encrypt the password
MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
byte[] hashedBytes;
UTF8Encoding encoder = new UTF8Encoding();
hashedBytes = md5Hasher.ComputeHash(encoder.GetBytes(strPassword));
SqlParameter paramPwd;
paramPwd = new SqlParameter("@Password", SqlDbType.Binary, 16);
paramPwd.Value = hashedBytes;
objCmd.Parameters.Add(paramPwd);
//Insert the record into the database
try
{
objConn.Open();
objCmd.ExecuteNonQuery();
return ValidationCode.LoginCreated;
}
catch
{
return ValidationCode.ConnectionFailed;
}
finally
{
objConn.Close();
}
}
您可以通过执行该函数来测试此方法。如果您尝试直接从数据库中选择信息,您会注意到加密已成功。现在,让我们进行验证。
验证登录
此方法允许您将登录与数据库中预先存在的登录进行验证。重要的是要注意,我们永远不会实际将数据返回给请求者进行评估。我们所有的评估都在服务器端完成;我们只将行计数返回给函数,这使其更加安全。
//Returns a validation code based on the control's set login info
public ValidationCode ValidateLogin(string tableName, string strLogin,
string strPassword, string loginColumn, string passColumn)
{
try
{
string strConnString = this.ConnectionString;
SqlConnection objConn = new SqlConnection(strConnString);
string strSQL = "SELECT COUNT(*) FROM " + tableName +
" WHERE " + loginColumn + "=@Username AND " + passColumn +
"=@Password;";
SqlCommand objCmd = new SqlCommand(strSQL, objConn);
//Create the parameters
SqlParameter paramUsername;
paramUsername = new SqlParameter("@Username", SqlDbType.VarChar, 25);
paramUsername.Value = strLogin;
objCmd.Parameters.Add(paramUsername);
//Hash the password
MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
byte[] hashedDataBytes;
UTF8Encoding encoder = new UTF8Encoding();
hashedDataBytes =
md5Hasher.ComputeHash(encoder.GetBytes(strPassword));
//Execute the parameterized query
SqlParameter paramPwd;
paramPwd = new SqlParameter("@Password", SqlDbType.Binary, 16);
paramPwd.Value = hashedDataBytes;
objCmd.Parameters.Add(paramPwd);
//The results of the count will be held here
int iResults;
try
{
objConn.Open();
//We use execute scalar, since we only need one cell
iResults = Convert.ToInt32(objCmd.ExecuteScalar().ToString());
}
catch
//Connection failure (most likely, though
//you can handle this exception however)
{
return ValidationCode.ConnectionFailed;
}
finally
{
objConn.Close();
}
if (iResults == 1)
return ValidationCode.LoginSucceeded;
else
return ValidationCode.LoginFailed;
}
catch
{
return ValidationCode.UnspecifiedFailure;
}
}
您可能已经注意到这两个方法具有相同的签名。很容易将它们合并为一个函数,但在这个示例中,我将它们分开。但这仅此而已。您现在可以创建页面或窗体上的点击事件,并调用其中一个函数,并相应地处理返回代码。再次说明,您不必使用返回代码;您可以轻松地处理异常或计数。
关注点
截至 2004 年,MD5 已存在已知的碰撞弱点。[来源:维基百科]“由于 MD5 只对数据进行一次遍历,如果能够构造出具有相同哈希值的两个前缀,那么就可以在它们后面添加一个相同的后缀,从而使碰撞更具合理性。而且,由于当前的碰撞查找技术允许任意指定前面的哈希状态,因此可以找到任意所需前缀的碰撞——对于任何给定的字符字符串 X,都可以确定两个以 X 开头的碰撞文件。生成两个碰撞文件所需的一切就是一个模板文件,其中包含一个 128 字节的数据块,该数据块对齐在 64 字节边界上,并且可以被碰撞查找算法自由更改。” 许多开发人员建议改用 WHIRLPOOL、SHA-1 或 RIPEMD-160。