Rickie Lee's blog

Welcome to my blog. I'm mainly involved in .Net platform and corresponding technologies. Thanks.

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  383 随笔 :: 3 文章 :: 1240 评论 :: 98 Trackbacks

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 on 2004-11-06 06:41 Rickie 阅读(4371) 评论(10)  编辑 收藏 所属分类: 9.企业级案例分析

评论

#1楼  2004-11-06 21:15 Avlee [未注册用户]
采用这种加密方法确实好,不过不知道用户如果忘记了密码,该怎么解决呢?
  回复  引用    

#2楼 [楼主] 2004-11-07 07:53 Rickie      
Avlee, 所见略同。《Duwamish密码分析篇 Part 3》将考虑这一问题。
  回复  引用  查看    

#3楼  2004-11-07 13:06 Avlee [未注册用户]
希望能有两者结合的参考方案
  回复  引用    

#4楼  2004-12-31 13:15 liupras [未注册用户]
如果用户自己忘了密码,可以让用户输入必要的验证信息后,例如:密码提示问题和密码提示答案,然后给用户初始化一个密码,例如:000000,这样用户可以继续登陆并且必要的时候修改为新密码。
  回复  引用    

#5楼  2005-03-30 11:40 Jammy Xu [未注册用户]
<DuwamishConfiguration>

<!-- Settings specific to the Duwamish application -->

<add key="Duwamish.DataAccess.ConnectionString" value="server=MachineA;User ID=Duwamish7_login;Password= password_<二十个随机字符>;database=Duwamish7;" />

</DuwamishConfiguration>

有沒有分析Web.config這個密碼是如何實現加解密的﹐我現在還沒弄明白
  回复  引用    

#6楼  2005-04-13 17:06 云之崖 [未注册用户]
<add key="Duwamish.DataAccess.ConnectionString" value="server=MachineA;User ID=Duwamish7_login;Password= password_<二十个随机字符>;database=Duwamish7;" />

我看,这二十个字符跟本就是明文的,不是加密的,好像安装项目时,Duwamish自己新创建了一个访问SQL的用户,这个用户的密码,就是明文的这二十个字符
  回复  引用    

#7楼  2006-05-24 10:51 Kent.Dean [未注册用户]
同樣的問題,如果用SHA散列后將不可逆,那是否意味著如果用戶忘記了密碼的情況下,將不可能再取回原密碼.
  回复  引用    

Duwamish7 的企业级框架开发,是一种很好的分层开发的方法论,尽管微软在编写Duwamish7时,使

用的是 VS 2003,但是这种分层思想在 VS 2005 中依然适用,只不过是代码的升级替换而已。本人通过近

半年的时间深入研究 Duwamish7,通过学习,自己的水平提高很快,同时,感到现在对于Duwamish7的学

习资料虽然很多,但是有提壶灌顶、从始至终,由浅入深的讲解课程不多,基于现状,我采用重现的方式

对Duwamish7 进行了一系列的讲解,重现该示例的设计分析过程,编写代码过程,在重现的过程中讲解示

例中的难点和设计思路。视频讲解是指采用屏幕录象技术,记录下我在计算机上的操作画面并配以同步的

语音讲解,从Duwamish7所具备的最初功能,到最后完全实现其所具备的所有功能。
在 www.leoon.com/asp.id=15 下,有一个本套讲解课程中的视频文件,该视频文件讲述的是

Duwamish7的框架搭建,因为Duwamish7是分层设计的,每一层其实就是一个程序集,这是理解Duwamish7

的最关键一个概念,同时那里还有整套视频讲解的目录。整套视频文件的播放时间有40多个小时,这里面

融会了我将近半年的心血。本视频出售的思路是如果您感兴趣,可以从www.leoon.com/asp.id=15中下载

免费视听的视频文件,如果这种讲解方式符合您的胃口,可以给我汇款,我收到后把视频文件刻盘后给您

邮寄过去。联系方式:Email:zxwyyz@yahoo.com.cn MSN:zxwyyz@yahoo.com.cn 每晚8:00-11:00

汇款地址:天津市河西区郁江道景观花园26-4-302 张晓伟收
注明:购买Duwamish7 视频
  回复  引用    

出售蓝奇高级验证码识别引擎,可准确识别新浪动网淘宝CSDN等多种复杂验证码。

输出为一个标准DLL,可供VB,VC,Delphi,C#.NET,VB.NET,模拟精灵,按键精灵等多平台调用,调用方法简单,几行代码即可完成。独具特色的边缘检测字符分离、旋转倾斜纠正和通用字符匹配算法(无论字体和大小), 使得该引擎对于像新浪、动网、淘宝、CSDN等多种验证码均有不错的识别率,是一款效果较为理想的验证码识别引擎。附详细的调用实例和代码注释等相关技术文档。

官方网站 - http://***/yzm_advocr
识别效果怎么样一试就知道 - DEMO下载 http://***/yzm_advocr/advocr.rar

  回复  引用