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將分析【用戶登錄】流程的密碼驗證過程。

 

發佈了17 篇原創文章 · 獲贊 0 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章