65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.58/5 (10投票s)

2006年1月4日

4分钟阅读

viewsIcon

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。

© . All rights reserved.