zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    怎么把共享文件夹显示在我的电脑
    window时间同步机制的简单介绍
    向指定服务器的指定端口发送UDP包
    窜口通信-读取时间码
    窜口通信-发送时间码
    回环网卡通信
    简单的TCP接受在转发到客户端的套接口
    国内能用的NTP服务器及和标准源的偏差值
    简单的UDP接受程序
    TCP包服务器接受程序
  • 原文地址:https://www.cnblogs.com/rickie/p/60990.html
Copyright © 2011-2022 走看看