Duwamish -- 密码分析篇, Part 2

本文深入解析了Duwamish7.0系统中密码的安全处理方式,包括SHA1散列运算、Salt运算及存储过程。探讨了密码验证流程,并提出在实际应用中密码找回的挑战。

Duwamish密码分析篇, Part 2

 

 

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

 

1,【用户登录】过程概述

Web 层中启动登录过程。用户输入电子邮件地址和密码(凭据),然后单击“Logon”(登录)按钮,这将调用 Duwamish7.Web.Logon.LogonButton_Click 方法。下一步,Duwamish7.Web.Logon.LogonButton_Click 方法创建密码的散列表示形式,并将凭据传递给业务外观层的 Duwamish7.BusinessFacade.CustomerSystem.GetCustomerByEmail 方法。接着 Duwamish7.DataAccess.Customers.LoadCustomerByEmail 方法调用数据访问层,后者又调用 GetCustomerByEmail 存储过程 (SPROC)。然后通过 ComparePasswords 方法,相对于从数据库中检索的经过 salt 和散列运算的密码来验证散列密码。如果凭据有效,则客户帐户信息成功地存储到 Cart 对象,并且 ASP.NET Forms 身份验证通过 pageBase ShoppingCart.Customer() 属性验证凭据。如果凭据无效,则 MismatchLabel 设置为可见,它在 ASP.NET 页上显示下面的内容:“Invalid email address or password- please try again”(电子邮件地址或密码无效,请再试一次)。

 

2,下面看看【用户登录】验证功能模块具体的实现代码

1Duwamish7.Web.Logon.LogonButton_Click 方法

该方法首先创建用户输入密码的SHA1散列形式,然后调用业务外观层的 Duwamish7.BusinessFacade.CustomerSystem.GetCustomerByEmail 方法。

    //

    // Check the Email and Password combination

    //

    SHA1 sha1 = SHA1.Create();

    byte [] password = sha1.ComputeHash(Encoding.Unicode.GetBytes(LogonPasswordTextBox.Text));

 

    custData = (new CustomerSystem()).GetCustomerByEmail(LogonEmailTextBox.Text, password);

   

    if (custData != null)   //were they valid?

    {

        //

        // 1. Update customer in session.

        // 2. Update customer in cart.

        //

        base.Customer = custData;

        base.ShoppingCart().Customer = custData;

        // 将已验证身份的用户重定向回最初请求的 URL

        FormsAuthentication.RedirectFromLoginPage("*", false);

    }

    else

    {

        MismatchLabel.Visible = true;

    }

 

如果凭据有效,则客户帐户信息成功地存储到 Cart 对象,并且 ASP.NET Forms 身份验证通过 pageBase ShoppingCart.Customer() 属性验证凭据。如果凭据无效,则 MismatchLabel 设置为可见,它在 ASP.NET 页上显示下面的内容:“Invalid email address or password- please try again”(电子邮件地址或密码无效,请再试一次)

 

2)业务外观层的 CustomerSystem.GetCustomerByEmail 方法

根据用户的email,获取DatabaseCustomerPassword,该Password已执行散列运算和对散列执行过Salt运算,是24个字节长度的byte数组。

public CustomerData GetCustomerByEmail(String emailAddress, byte [] password)

{

      //

      // Check preconditions

      //

      ApplicationAssert.CheckCondition(emailAddress != String.Empty, "Email address is required", ApplicationAssert.LineNumber);

      ApplicationAssert.CheckCondition(password.Length != 0, "Password is required", ApplicationAssert.LineNumber);

      //

      // Get the customer dataSet

      //

      CustomerData dataSet;

      using (DataAccess.Customers customersDataAccess = new DataAccess.Customers())

      {

           dataSet = customersDataAccess.LoadCustomerByEmail(emailAddress);

      }

      //   

      // Verify the customer's password

      //

      DataRowCollection rows = dataSet.Tables[CustomerData.CUSTOMERS_TABLE].Rows;

 

      if ( ( rows.Count == 1 ))

      {

           byte [] dbPassword = (byte[])rows[0][CustomerData.PASSWORD_FIELD];

 

           if (ComparePasswords (dbPassword, password))

                 return dataSet;

           else

                 return null;

      }

      else

           return null;

}

 

在获取到DataAccess层返回的CustomerData对象后,进一步调用类中的私有方法ComparePasswords()。该方法负责从Password字段值中提取Salt值,然后运用该Salt值对传入的SHA1散列执行Salt运算。

 

其中CreateSaltedPassword()方法和前面【用户注册】过程相同,用来对散列结果再次执行Salt运算。

// compare the hashed password against the stored password

private bool ComparePasswords(byte[] storedPassword, byte[] hashedPassword)

{

      if (storedPassword == null || hashedPassword == null || hashedPassword.Length != storedPassword.Length - saltLength)

           return false;

 

      // get the saved saltValue

      // 获取已保存在password数据字段中的Salt

      byte[] saltValue = new byte[saltLength];

      int saltOffset = storedPassword.Length - saltLength;

      for (int i = 0; i < saltLength; i++)

           saltValue[i] = storedPassword[saltOffset + i];

 

      byte[] saltedPassword = CreateSaltedPassword(saltValue, hashedPassword);

 

      // compare the values

      return CompareByteArray(storedPassword, saltedPassword);

}

 

按字节比较两个字节数组是否相等,分别传入数据表中Password字段byte数组和对当前散列执行Salt运算后的byte数组。

// compare the contents of two byte arrays

private bool CompareByteArray(byte[] array1, byte[] array2)

{

      if (array1.Length != array2.Length)

           return false;

      for (int i = 0; i < array1.Length; i++)

      {

           if (array1[i] != array2[i])

                 return false;

      }

      return true;

}

 

3)数据访问层的Customers.LoadCustomerByEmail()方法

该方法根据传入的emailAddress参数,查询Database,返回CustomerData对象。过程比较简单,详细代码请查询Duwamish 7.0范例。

 

3Summary

通过对Duwamish 7.0范例中用户注册和用户登录验证过程的分析,我们确信用户Password以安全的Salt运算结果存放在后台的Database中。

其实,在实际的应用系统中,上述的加密过程有一个小问题:就是当有大量的用户忘记了自己的Password,如何帮助他们恢复自己的Password呢?这个问题地球人都不知道,只有通过另外的application来将这些Password重新Update为新的Password,因为散列是单向操作,使用散列算法对原始密码加密后将无法再恢复。

 

因此,将在Duwamish密码分析篇, Part 3中分析如何实现双向的加密/解密操作,来克服上面提出的问题。

下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中一个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这一议题中,电路板被构建为一个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的一种常用策略。 该算法与回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的一个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排列树,在探索过程中,每个节点仅被赋予一次成为扩展节点的机会,且会一次性生成其全部子节点。 针对布线问题的解决,队列式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将与该扩展节点相邻且可通行的方格加入至活跃节点队列中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队列中提取队首节点作为下一个扩展节点,并将与当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队列。 这一过程将持续进行,直至算法探测到目标方格 b 或活跃节点队列为空。 在实现上述算法时,必须定义一个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和列。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值