Duwamish密码分析篇, Part 1

Duwamish密码分析篇, Part 1

 

Written by: Rickie Lee

Nov. 05, 2004

 

继续前面关于DuwamishPOST,这里将学习Duwamish中关于Password的处理方式。Duwamish 7.0范例中的帐户密码通过SHA1散列运算和对散列执行Salt运算后,是以byte形式存放在Database中,避免明文的方式,以提高系统的安全性。

 

Duwamish的用户注册部分是封装在\web\modules\accountmodule.ascx用户控件内。随便提一下,Duwamish web tier中采用了大量的user control,并且所有的user control都继承\web\ModuleBase.cs 类,与web page继承PageBase.cs类相似,这种做法值得推荐。Duwamishuser control主要是封装一些相应的功能,模块化。这样不仅可以在本web项目内重用,而且以后维护也比较方便,如\web\modules\accountmodule.ascx user control就封装了用户注册部分的功能。

 

下面看看【用户注册】功能模块具体的实现代码(\web\modules\accountmodule.ascx):

1获取用户登记/注册password,并帐户密码执行散列运算。

byte [] bytePassword = null;

String tmpPassword = PasswordTextBox.Text;

 

if (tmpPassword == ConfirmPasswordTextBox.Text)

{

    SHA1 sha1 = SHA1.Create();

    bytePassword = sha1.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));

}

……

retVal = (new CustomerSystem()).CreateCustomer(EmailTextBox.Text,

                                           bytePassword,

                                           AcctNameTextBox.Text,

                                           AddressTextBox.Text,

                                           CountryTextBox.Text,

                                           PhoneTextBox.Text,

                                           FaxTextBox.Text,

                                           out moduleCustomerInfo);

 

先使用实现 160 SHA-1 标准的 System.Security.Cryptography 命名空间对密码进行散列运算。然后调用BusinessFacade\CustomerSystem类的CreateCustomer()方法。

 

知识点:

散列简介

散列(Hash)是一种单向算法,一旦数据被转换,将无法再获得其原始值。大多数开发人员使用数据库存储密码,如果密码直接以明文的形式存放在数据库中,则开发人员也能够看到这些密码,甚至包括用户的Credit Card信息。

不过,我们可以使用散列算法对密码进行加密,然后再将其存储在数据库中。用户输入密码后,可以再次使用散列算法对其进行转换,然后将其与存储在数据库中的散列进行比较。散列的特点之一是,即使原始数据只发生一个小小的改动,数据的散列也会发生非常大的变化。Rickie Ricky 这两个单词非常相似,但使用散列算法加密后的结果却相去甚远。你可能根本看不出二者之间有什么相似之处。

 

.NET 开发人员可以使用多种散列算法类。最常用的是 SHA1 MD5。下面我们看一下如何为Rickie这样的普通字符串生成散列,使任何人都无法识别它。

1)使用 SHA1 生成散列

通过如下的示例代码,来演示如何通过SHA1生成散列:

byte [] bytePassword = null;

string tmpPassword = txtPassword.Text.Trim();

 

// 创建新的加密服务提供程序对象

SHA1 sha1 = SHA1.Create();

// 将原始字符串转换成字节数组,然后计算散列,并返回一个字节数组

bytePassword = sha1.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));

// Releases all resources used by the System.Security.Cryptography.HashAlgorithm.

sha1.Clear();

// 返回散列值的 Base64 编码字符串

txtResults.Text = Convert.ToBase64String(bytePassword);

 

传递不同的字符串值来调用该例程,查看散列值的变化。例如,如果将字符串Rickie传递给该例程,输出结果:

v8ocXHBvlh4EqY/2HsJNH5XBVG0=

现在,将此过程中的输入值更改为Ricky。你将看到以下输出结果:

luQsSa61sB/7PT9piDx+OAGqCnI=

 

如此可见,输入字符串的一个小小变化就会产生完全不同的字符组合。这正是散列算法之所以有效的原因,它使我们很难找到输入字符串的规律,也很难根据加密后的字符弄清楚字符串原来的模样。

 

2)使用MD5也可以生成散列

通过如下的示例代码,来演示如何通过MD5生成散列:

byte [] bytePassword = null;

string tmpPassword = txtPassword.Text.Trim();

 

MD5 md5 = MD5.Create();

bytePassword = md5.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));

// Releases all resources used by the System.Security.Cryptography.HashAlgorithm.

md5.Clear();

txtResults.Text = Convert.ToBase64String(bytePassword);

 

输入RickieMD5散列算法的输出结果:

YUqR1JfNxrciyG0ixNj58A==

 

同样,加密后的字符串看起来也与原始输入相去甚远。这些散列算法对于创建没有任何意义的密码来说非常有用,也使黑客很难猜出这些密码。之所以使用散列算法,是因为可以用这种算法对密码进行加密并将其存储在数据库中。然后,当用户输入真实密码时,需要先对用户输入的密码进行同样的散列,然后通过网络发送到数据库中,比较它与数据库中的密码是否匹配。

 

请记住,散列是单向操作。使用散列算法对原始密码加密后将无法再恢复。

 

上述两种散列算法都执行同一种操作。不同之处只在于生成散列的密钥大小以及使用的算法。使用的密钥越大,加密就越安全。例如,MD5 使用的加密密钥比 SHA1 使用的密钥大,因此 MD5 散列较难破解。

 

对于散列算法要考虑的另外一点是,从实践或理论的角度上看是否存在冲突的可能性。冲突是我们所不希望的,因为两个不同的单词可能会生成相同的散列。例如,SHA1 从实践或理论上来讲没有发生冲突的可能性。MD5 从理论上讲有发生冲突的可能性,但从实践上讲没有发生冲突的可能性。因此,选择哪种算法归根结底取决于所需要的安全级别。

 

3Summary

一般情况下,将上述加密的字节数组,通过使用Convert.ToBase64String(bytePassword)方法把字节数组转换成 Base64 编码的字符串,然后存储在数据库中即可完成一般的商业应用。

 

 

2,调用BusinessFacade\CustomerSystem类,对散列执行Salt运算。

到目前为止,散列算法暴露出来的问题之一是,如果两个用户碰巧使用相同的密码,那么散列值将完全相同。如果黑客看到您存储密码的表格,会从中找到规律并明白您很可能使用了常见的词语,然后黑客会开始词典攻击以确定这些密码。要确保任何两个用户密码的散列值都不相同,一种方法是在加密密码之前,在每个用户的密码中添加一个唯一的值。这个唯一值称为“盐”值(Salt)。

 

虽然对密码执行散列运算是一个好的开端,但若要增加免受潜在攻击的安全性,则可以对密码散列执行 Salt 运算。Salt 就是在已执行散列运算的密码中插入的一个随机数字。这一策略有助于阻止潜在的攻击者利用预先计算的字典攻击。字典攻击是攻击者使用密钥的所有可能组合来破解密码的攻击。当您使用 Salt 值使散列运算进一步随机化后,攻击者将需要为每个 Salt 值创建一个字典,这将使攻击变得非常复杂且成本极高。

 

Salt 值随散列存储在一起,并且未经过加密。所存储的 Salt 值可以在随后用于密码验证。

 

下面看看Duwamish 7.0中是如何实现Salt运算:

1BusinessFacade\CustomerSystem classCreate Customer()方法

public bool CreateCustomer(String emailAddress,

                           byte [] password,

                           String name,

                           String address,

                           String country,

                           String phoneNumber,

                           String fax,

                           out CustomerData custData)

{

    // create a salted password

    byte [] saltedPassword = CreateDbPassword(password);

 

    //

    // Create a new row

    //

    custData = new CustomerData();

   

    DataTable table = custData.Tables[CustomerData.CUSTOMERS_TABLE];

    DataRow row = table.NewRow();

    //

    // Fill input data into new row

    //

    row[CustomerData.EMAIL_FIELD] = emailAddress;

    row[CustomerData.PASSWORD_FIELD] = saltedPassword;

    row[CustomerData.NAME_FIELD] = name;

    row[CustomerData.ADDRESS_FIELD] = address;

    row[CustomerData.COUNTRY_FIELD] = country;

    row[CustomerData.PHONE_FIELD] = phoneNumber;

    row[CustomerData.FAX_FIELD] = fax;

    //

    // Add it to the table

    //

    table.Rows.Add(row);

    // 调用Business rules tierCustomer Class

    // Insert the customer using the business rules

    //

    return (new Customer()).Insert(custData);

}

首先调用Facade\CustomerSystem 类的私有方法CreateDbPassword(),获取对散列执行Salt运算结果(长度为24个字节的byte数组),然后调用Business rules tier中的Customer classInsert()方法,将用户信息,包括密码存放在数据库中。

 

2Facade\CustomerSystem 类的私有方法 CreateDbPassword()

// create salted password to save in Db

private byte [] CreateDbPassword(byte[] unsaltedPassword)

{

          //Create a salt value

          byte[] saltValue = new byte[saltLength];

          RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

          //用加密型强随机字节填充的数组

          rng.GetBytes(saltValue);

         

          return CreateSaltedPassword(saltValue, unsaltedPassword);

}

上述代码片断使用 .NET Framework RNGCryptoServiceProvider 创建一个随机的数字字符串。RNG 表示随机数生成器。该类可以创建一个任意长度的随机字节数组,长度由您指定。您可以使用此随机字节数组作为散列算法的Salt值。要采用这种方法,必须安全地存储该Salt值。

 

saltLength=4(常量),Duwamish 7 示例用RNGCryptoServiceProvider创建一个 4 字节 Salt 值。然后调用Facade\CustomerSystem 类的私有方法CreateSaltedPassword(),获取对散列执行Salt运算后的结果。

 

3Facade\CustomerSystem 类的私有方法CreateSaltedPassword()

// create a salted password given the salt value

private byte[] CreateSaltedPassword(byte[] saltValue, byte[] unsaltedPassword)

{

          // add the salt to the hash

          byte[] rawSalted  = new byte[unsaltedPassword.Length + saltValue.Length];

         

          // Copies all the elements of the current one-dimensional System.Array to the specified one-dimensional System.Array starting at the specified destination System.Array index.

         

          unsaltedPassword.CopyTo(rawSalted,0);

          saltValue.CopyTo(rawSalted,unsaltedPassword.Length);

         

          //Create the salted hash                      

          SHA1 sha1 = SHA1.Create();

          byte[] saltedPassword = sha1.ComputeHash(rawSalted);

 

          // add the salt value to the salted hash

          byte[] dbPassword  = new byte[saltedPassword.Length + saltValue.Length];

          saltedPassword.CopyTo(dbPassword,0);

          saltValue.CopyTo(dbPassword,saltedPassword.Length);

 

          return dbPassword;

}

 

该方法根据传入的Salt值(长度为4个字节的byte数组)和已执行散列运算的密码(长度为20个字节的byte数组),拼接为长度为24byte数组。然后对上述拼接后的数组再进行SHA1散列运算,得到结果saltedPassword(长度为20个字节的byte数组)。

 

最后将saltedPassword(长度为20个字节的byte数组)和Salt值(长度为4个字节的byte数组)拼接为dbPassword(长度为4个字节的byte数组)返回。

 

3,调用BusinessRules\Customer类的Insert()方法。

Insert()方法根据传入的CustomerData对象,验证数据的合法性,然后调用Data Access tierCustomers对象的InsertCustomer()方法。

具体代码请参考Duwamish 7.0范例。

 

4,调用DataAccess\Customers类的InsertCustomer()方法。

InsertCustomer()方法根据传入的CustomerData对象,调用Database端的Stored Procedure,执行真正的数据库insert操作。可以观察到Duwamish7 DatabaseCustomers表的Password字段类型为binary且长度为24

具体代码请参考Duwamish 7.0范例。

 

下一篇POSTDuwamish密码分析篇 Part 2将分析【用户登录】流程的密码验证过程。

 

 

References:

1, MSDN, Duwamish 7.0

2, Paul D. Sheriff, Microsoft .NET 中的简化加密, http://www.microsoft.com/china/MSDN/library/archives/library/dnnetsec/html/cryptosimplified.asp
posted @ 2004-11-06 06:41  Rickie  阅读(5605)  评论(9编辑  收藏  举报