Microsoft .NET 中的簡(jiǎn)化加密
2024-07-10 12:58:45
供稿:網(wǎng)友
 
適用于:
 microsoft® .net 
 安全
 microsoft® visual basic® .net
 c#
摘要:學(xué)習(xí)如何利用 .net framework 的加密功能創(chuàng)建類似本文所述的包裝程序來(lái)保護(hù)您的數(shù)據(jù)。 
下載與本文相關(guān)的 cryptosamplecssample.msi 和 cryptosamplevbsample.msi 代碼示例。(請(qǐng)注意,在示例文件中,程序員的注釋使用的是英文,本文中將其譯為中文是為了便于讀者理解。)
目錄
散列簡(jiǎn)介
創(chuàng)建示例散列項(xiàng)目
在散列中添加“鹽”值
小結(jié)
您希望在計(jì)算機(jī)上保存一些機(jī)密信息嗎?如果是,本文就為您介紹如何進(jìn)行加密!加密技術(shù)就是將有意義的字符編碼成無(wú)意義的字符,使不應(yīng)該訪問(wèn)這些數(shù)據(jù)的人員無(wú)法讀取它們。加密技術(shù)已經(jīng)存在很多年了,甚至遠(yuǎn)在計(jì)算機(jī)誕生之前就已經(jīng)存在了。隨著計(jì)算機(jī)的出現(xiàn),在計(jì)算機(jī)領(lǐng)域應(yīng)用加密技術(shù)可以生成幾乎牢不可破的代碼。microsoft 在 windows 95 中開(kāi)發(fā)并分發(fā)了加密 api。使用 microsoft .net,新創(chuàng)建的類可以將這些復(fù)雜的算法打包到非常易于使用的屬性和方法中。
散列簡(jiǎn)介
如果您只是不想讓別人竊取您的密碼,那么可以為密碼數(shù)據(jù)創(chuàng)建一個(gè)散列。散列是一種單向算法,一旦數(shù)據(jù)被轉(zhuǎn)換,將無(wú)法再獲得其原始值。大多數(shù)開(kāi)發(fā)人員使用數(shù)據(jù)庫(kù)存儲(chǔ)密碼。但是,在數(shù)據(jù)庫(kù)中查找用戶數(shù)據(jù)的人員也能夠看到這些密碼。不過(guò),您可以使用散列算法對(duì)密碼進(jìn)行加密,然后再將其存儲(chǔ)在數(shù)據(jù)庫(kù)中。用戶輸入密碼后,您可以再次使用散列算法對(duì)其進(jìn)行解密,然后將其與存儲(chǔ)在數(shù)據(jù)庫(kù)中的散列進(jìn)行比較。散列的缺點(diǎn)之一是,即使原始數(shù)據(jù)只發(fā)生一個(gè)小小的改動(dòng),數(shù)據(jù)的散列也會(huì)發(fā)生非常大的變化。pork 和 porky 這兩個(gè)單詞非常相似,但使用散列算法加密后的結(jié)果卻相去甚遠(yuǎn)。您可能根本看不出二者之間有什么相似之處。
.net 開(kāi)發(fā)人員可以使用多種散列算法類。最常用的是 sha1 和 md5。下面我們看一下如何為“paul”這樣的普通字符串生成散列,使任何人都無(wú)法識(shí)別它。
使用 sha1 生成散列
我們創(chuàng)建一個(gè)新例程,然后使用它為字符串“paul”生成散列。在 visual studio® .net 中打開(kāi)一個(gè)新的 windows 應(yīng)用程序,在窗體上放置一個(gè)命令按鈕。當(dāng)該命令按鈕上發(fā)生 click 事件時(shí),將調(diào)用名為 hashtext() 的方法。您可以將以下代碼添加到該窗體中,看一下此散列算法的實(shí)際效果。編寫下列代碼之前,需要導(dǎo)入命名空間 system.security.cryptography。
private sub hashtext(byval texttohash as string)
 dim sha1 as sha1cryptoserviceprovider
 dim bytvalue() as byte
 dim bythash() as byte
 ' 創(chuàng)建新的加密服務(wù)提供程序?qū)ο?br> sha1 = new sha1cryptoserviceprovider
 ' 將原始字符串轉(zhuǎn)換成字節(jié)數(shù)組
 bytvalue = _
 system.text.encoding.utf8.getbytes(texttohash)
 ' 計(jì)算散列,并返回一個(gè)字節(jié)數(shù)組
 bythash = sha1.computehash(bytvalue)
 sha1.clear()
 ' 返回散列值的 base64 編碼字符串
 debug.writeline(convert.tobase64string(bythash))
end sub
您可以傳遞不同的字符串值來(lái)調(diào)用該例程,查看散列值的變化。例如,如果將字符串“paul”傳遞給該例程,debug(調(diào)試)窗口將顯示以下文本:
w2h6uygmjt/nq5zqihcbteaxwv8=
現(xiàn)在,將此過(guò)程中的輸入值更改為“pauly”。您將看到以下輸出結(jié)果:
proywxj0znmpgf5sbb18+7gsasm=
如您所見(jiàn),輸入字符串的一個(gè)小小變化就會(huì)產(chǎn)生完全不同的字符組合。這正是散列算法之所以有效的原因,它使我們很難找到輸入字符串的規(guī)律,也很難根據(jù)加密后的字符弄清楚字符串原來(lái)的模樣。
使用 md5 也可以生成散列
了解一種散列類的使用方法后,基本上就了解了所有的散列類。下面的方法用于 md5 散列算法。注意,除了 cryptoserviceprovider 類不同外,代碼是完全相同的。
private sub hashtextmd5(byval texttohash as string)
 dim md5 as md5cryptoserviceprovider
 dim bytvalue() as byte
 dim bythash() as byte
 ' 創(chuàng)建新的加密服務(wù)提供程序?qū)ο?br> md5 = new md5cryptoserviceprovider
 ' 將原始字符串轉(zhuǎn)換成字節(jié)數(shù)組
 bytvalue = system.text.encoding. _
 utf8.getbytes(texttohash)
 ' 計(jì)算散列,并返回一個(gè)字節(jié)數(shù)組
 bythash = md5.computehash(bytvalue)
 md5.clear()
 ' 返回散列值的 base64 編碼字符串
 debug.writeline(convert.tobase64string(bythash))
end sub
輸入“paul”之后,md5 散列算法的輸出結(jié)果如下所示:
nvwbshh1mknctpiosyqytq==
同樣,加密后的字符串看起來(lái)也與原始輸入相去甚遠(yuǎn)。這些散列算法對(duì)于創(chuàng)建沒(méi)有任何意義的密碼來(lái)說(shuō)非常有用,也使黑客很難猜出這些密碼。之所以使用散列算法,是因?yàn)榭梢杂眠@種算法對(duì)密碼進(jìn)行加密并將其存儲(chǔ)在數(shù)據(jù)庫(kù)中。然后,當(dāng)用戶輸入真實(shí)密碼時(shí),您先對(duì)密碼進(jìn)行解密,然后通過(guò)網(wǎng)絡(luò)發(fā)送到數(shù)據(jù)庫(kù)中,比較它與數(shù)據(jù)庫(kù)中的密碼是否匹配。請(qǐng)記住,散列是單向操作。使用散列算法對(duì)原始密碼加密后將無(wú)法再恢復(fù)。
如何選擇算法
本文介紹的兩種散列算法都執(zhí)行同一種操作。不同之處只在于生成散列的密鑰大小以及使用的算法。使用的密鑰越大,加密就越安全。例如,md5 使用的加密密鑰比 sha1 使用的密鑰大,因此 md5 散列較難破解。
對(duì)于散列算法要考慮的另外一點(diǎn)是,從實(shí)踐或理論的角度上看是否存在沖突的可能性。沖突是我們所不希望的,因?yàn)閮蓚€(gè)不同的單詞可能會(huì)生成相同的散列。例如,sha1 從實(shí)踐或理論上來(lái)講沒(méi)有發(fā)生沖突的可能性。md5 從理論上講有發(fā)生沖突的可能性,但從實(shí)踐上講沒(méi)有發(fā)生沖突的可能性。因此,選擇哪種算法歸根結(jié)底取決于您所需要的安全級(jí)別。
創(chuàng)建示例散列項(xiàng)目
本文包含兩個(gè)示例散列項(xiàng)目,以更普通的方式說(shuō)明如何使用不同的散列算法加密任意字符串。這兩個(gè)示例項(xiàng)目的名稱分別為 cryptosamplevb.sln 和 cryptosamplecs.sln。前者是 visual basic .net 解決方案,后者是 c# 解決方案。兩個(gè)解決方案都包括一個(gè)類似圖 1 的窗體,該窗體允許您輸入要通過(guò)散列算法為其加密的原始字符串,還提供一個(gè)用來(lái)選擇散列算法的選項(xiàng)按鈕和一個(gè)顯示散列結(jié)果的文本框。
圖 1:創(chuàng)建一個(gè)通用散列屏幕來(lái)嘗試兩種散列算法。
單擊此屏幕上的 hash(散列)按鈕時(shí),將運(yùn)行該按鈕的 click 事件過(guò)程。此事件過(guò)程將調(diào)用一個(gè)名為 hashstring() 的例程。
' visual basic .net
private sub btnhash_click(byval sender as system.object, _
 byval e as system.eventargs) handles btnhash.click
 txthashed.text = hashstring(txtoriginal.text)
end sub
// c#
private void cmdhash_click(object sender, 
 system.eventargs e)
{
 txthashed.text = hashstring(txtoriginal.text);
}
hashstring() 方法接受輸入的值并調(diào)用 sethash() 方法。此方法將根據(jù)窗體上選項(xiàng)按鈕的設(shè)置來(lái)決定使用哪個(gè)加密服務(wù)提供程序創(chuàng)建該方法的實(shí)例并返回該方法。將為該窗體創(chuàng)建一個(gè)名為 mhash 的 hashalgorithm 類型的成員變量。hashalgorithm 類型是創(chuàng)建所有散列加密服務(wù)提供程序的基類。
' visual basic .net
private mhash as hashalgorithm
// c#
private hashalgorithm mhash;
sethash() 方法如下所示:
' visual basic .net
private function sethash() as hashalgorithm
 if optsha1.checked then
 return new sha1cryptoserviceprovider
 else
 if optmd5.checked then
 return new md5cryptoserviceprovider
 end if
 end if
end function
// c#
private hashalgorithm sethash()
{
 if(this.optsha1.checked)
 return new sha1cryptoserviceprovider();
 else
 return new md5cryptoserviceprovider();
}
根據(jù)您在窗體上選擇的選項(xiàng)按鈕,此方法將創(chuàng)建并返回一個(gè)不同的 hashalgorithm 類型。hashstring() 方法在該窗體上執(zhí)行實(shí)際的數(shù)據(jù)加密:
' visual basic .net
private function hashstring(byval value as string) _
 as string
 dim bytvalue() as byte
 dim bythash() as byte
 ' 創(chuàng)建新的加密服務(wù)提供程序?qū)ο?br> mhash = sethash()
 ' 將原始字符串轉(zhuǎn)換成字節(jié)數(shù)組
 bytvalue = system.text.encoding.utf8.getbytes(value)
 ' 計(jì)算散列,并返回一個(gè)字節(jié)數(shù)組
 bythash = mhash.computehash(bytvalue)
 mhash.clear()
 ' 返回散列值的 base64 編碼字符串
 return convert.tobase64string(bythash)
end function
// c#
private string hashstring(string value)
{
 mhash = sethash();
 // 將原始字符串轉(zhuǎn)換成字節(jié)數(shù)組
 byte[] bytvalue = system.text.encoding.utf8.getbytes(value);
 // 計(jì)算散列,并返回一個(gè)字節(jié)數(shù)組
 byte[] bythash = mhash.computehash(bytvalue);
 mhash.clear();
 // 返回散列值的 base64 編碼字符串
 return convert.tobase64string(bythash);
}
在 hashstring 方法中,我們創(chuàng)建了兩個(gè)字節(jié)數(shù)組。第一個(gè)數(shù)組用來(lái)保存用戶的原始字符串輸入。我們使用 system.text.encoding.utf8.getbytes() 方法將該字符串轉(zhuǎn)換成字節(jié)數(shù)組。將原始字符串轉(zhuǎn)換成字節(jié)數(shù)組后,現(xiàn)在使用服務(wù)提供程序的 computehash() 方法計(jì)算該字符串的散列值。此方法接受字節(jié)數(shù)組作為輸入,然后返回該字符串加密格式的字節(jié)數(shù)組。
注意:完成之后清除散列變量是一個(gè)好的做法。因此,您看到計(jì)算該字符串的散列后,我們調(diào)用了 clear 方法。
現(xiàn)在我們已經(jīng)獲得了加密的字節(jié)數(shù)組,這正是要從該方法返回的數(shù)組。因?yàn)槲覀円獙⒃贾岛图用苤刀甲鳛樽址當(dāng)?shù)據(jù)類型而不是字節(jié)數(shù)組進(jìn)行處理,所以要通過(guò)使用 convert.tobase64string 方法返回加密的字節(jié)。此方法負(fù)責(zé)將字節(jié)數(shù)組轉(zhuǎn)換成 base64 編碼的字符串。base64 編碼的使用非常重要,因?yàn)橛锌赡苄枰獙⒋俗址频?web 頁(yè)上或?qū)⑵浯鎯?chǔ)到數(shù)據(jù)庫(kù)中。如果不進(jìn)行轉(zhuǎn)換,加密字符串中的某些高階 ascii 字符將無(wú)法正確顯示或存儲(chǔ)。
在散列中添加一些“鹽”值
到目前為止,散列算法暴露出來(lái)的問(wèn)題之一是,如果兩個(gè)用戶碰巧使用相同的密碼,那么散列值將完全相同。如果黑客看到您存儲(chǔ)密碼的表格,會(huì)從中找到規(guī)律并明白您很可能使用了常見(jiàn)的詞語(yǔ),然后黑客會(huì)開(kāi)始詞典攻擊以確定這些密碼。要確保任何兩個(gè)用戶密碼的散列值都不相同,一種方法是在加密密碼之前,在每個(gè)用戶的密碼中添加一個(gè)唯一的值。這個(gè)唯一值稱為“鹽”值。在進(jìn)行此操作時(shí),需要確保將使用的鹽值存儲(chǔ)為用戶記錄的一部分。如果您使用表格存儲(chǔ)用戶 id 和密碼,我建議您使用不同的表格來(lái)存儲(chǔ)鹽值。這樣,即使數(shù)據(jù)庫(kù)泄漏,鹽值也可以為您提供一層額外的安全保護(hù)。
在用戶密碼中添加鹽值的方法有多種。最簡(jiǎn)單的方法是摘取用戶的某些信息(例如姓、名、電子郵件地址或員工 id)并將其添加到用戶密碼中,然后再進(jìn)行加密。這種方法的缺點(diǎn)是,因?yàn)槟枰鎯?chǔ)鹽值,所以如果黑客找到該值,將會(huì)對(duì)您所做的一切操作了如指掌。當(dāng)然,黑客需要花費(fèi)額外的時(shí)間來(lái)破解鹽值,但這對(duì)黑客來(lái)說(shuō)簡(jiǎn)直是易如反掌。
另外一種方法是使用 .net framework 類 rngcryptoserviceprovider 創(chuàng)建一個(gè)隨機(jī)的數(shù)字字符串。rng 表示隨機(jī)數(shù)生成器。該類可以創(chuàng)建一個(gè)任意長(zhǎng)度的隨機(jī)字節(jié)數(shù)組,長(zhǎng)度由您指定。您可以使用此隨機(jī)字節(jié)數(shù)組作為散列算法的鹽值。要采用這種方法,必須安全地存儲(chǔ)該鹽值。
在圖 2 所示的示例中,您需要在文本框中輸入一個(gè)字符串,選擇特定的散列類型,然后生成鹽值以及包含該鹽值和原始字符串的散列值。
圖 2:在散列值中添加鹽值以創(chuàng)建更安全的密碼散列
(需要存儲(chǔ)鹽值以便再次創(chuàng)建相同的散列。)
該示例與本文中的上一個(gè)示例基本相同,不同之處在于創(chuàng)建鹽值的例程。在此屏幕上的按鈕的 click 事件下,首先調(diào)用一個(gè)名為 createsalt() 的方法來(lái)生成一個(gè)唯一的鹽值,然后將該值存儲(chǔ)到 txtsalt 文本框中。獲得唯一的鹽值后,再調(diào)用 hashstring() 方法,將這兩個(gè)值結(jié)合起來(lái)。
' visual basic .net
private sub btnhash_click(byval sender as system.object, _
 byval e as system.eventargs) handles btnhash.click
 txtsalt.text = createsalt()
 txthashed.text = hashstring(txtsalt.text & _
 txtoriginal.text)
end sub
// c#
private void cmdhash_click(object sender, system.eventargs e)
{
 txtsalt.text = createsalt();
 txthashed.text = hashstring(txtoriginal.text);
}
createsalt() 方法的代碼非常簡(jiǎn)單。它首先創(chuàng)建一個(gè)長(zhǎng)度為 8 個(gè)字節(jié)的字節(jié)數(shù)組,然后您創(chuàng)建一個(gè)新的 rngcryptoserviceprovider 類實(shí)例。使用該對(duì)象的 getbytes() 方法,將生成的隨機(jī)字符集填充到字節(jié)數(shù)組中。然后將此字節(jié)數(shù)組轉(zhuǎn)換成 base64 編碼字符串并從該函數(shù)返回。
' visual basic .net
private function createsalt() as string
 dim bytsalt(8) as byte
 dim rng as new rngcryptoserviceprovider
 rng.getbytes(bytsalt)
 return convert.tobase64string(bytsalt)
end function
// c#
private string createsalt()
{
 byte[] bytsalt = new byte[8];
 rngcryptoserviceprovider rng;
 rng = new rngcryptoserviceprovider();
 rng.getbytes(bytsalt);
 return convert.tobase64string(bytsalt);
}
數(shù)據(jù)加密是一個(gè)雙行道
如果需要在兩個(gè)或多個(gè)人員或計(jì)算機(jī)之間來(lái)回發(fā)送信息,并希望對(duì)方能夠讀取數(shù)據(jù),而其他人不可以讀取,那么加密則是最好的方式!加密算法使您可以將數(shù)據(jù)掩蓋起來(lái),除了特定人員能夠?qū)ζ浣饷芡猓渌藛T不大可能通過(guò)數(shù)學(xué)方法讀取該數(shù)據(jù)。但如果您希望某個(gè)人能夠讀取該數(shù)據(jù),您可以為其提供一個(gè)特定的“密鑰”,使其能夠解密并讀取數(shù)據(jù)。.net framework 中有多種可用的加密/解密算法。本文主要介紹對(duì)稱算法,包括以下幾種: 
des 
rc2 
rijndael 
tripledes 
對(duì)稱算法(或密鑰算法)使用一個(gè)密鑰和一個(gè)初始化向量 (iv) 來(lái)保證數(shù)據(jù)的安全。使用該數(shù)據(jù)的雙方都必須知道這個(gè)密鑰和初始化向量才能夠加密和解密數(shù)據(jù)。必須確保該密鑰的安全,否則其他人將有可能解密該數(shù)據(jù)并讀取該消息。初始化向量只是一個(gè)隨機(jī)生成的字符集,使用它可以確保任何兩個(gè)文本都不會(huì)生成相同的加密數(shù)據(jù)。使用 .net 中不同的加密類的內(nèi)置方法可以導(dǎo)出密鑰,至于如何導(dǎo)出密鑰,則不屬于本文要討論的內(nèi)容。
其他類型的加密算法稱為不對(duì)稱算法。不對(duì)稱算法使用公鑰/私鑰對(duì)來(lái)創(chuàng)建加密數(shù)據(jù)。不對(duì)稱算法將在下文進(jìn)行討論。
如何在不同的情況下選擇不同的加密方法
對(duì)稱算法(或密鑰算法)的速度非常快,非常適于加密大型的數(shù)據(jù)流。這些算法可以加密數(shù)據(jù),也可以解密數(shù)據(jù)。它們都相當(dāng)安全,但如果有足夠的時(shí)間,也可能會(huì)被破密,因?yàn)橛腥丝赡軙?huì)搜索每個(gè)已知的密鑰值組合。由于每種算法都使用固定的密鑰長(zhǎng)度或 ascii 字符,因此計(jì)算機(jī)程序可以嘗試每個(gè)可能的密鑰組合并最終找到正確的那個(gè)組合。這些類型的算法一般用于存儲(chǔ)和檢索數(shù)據(jù)庫(kù)的連接字符串。
不對(duì)稱算法(或公鑰算法)沒(méi)有對(duì)稱算法快,但其代碼較難破密。這些算法取決于兩個(gè)密鑰,一個(gè)是私鑰,另一個(gè)是公鑰。公鑰用來(lái)加密消息,私鑰是可以解密該消息的唯一密鑰。公鑰和私鑰通過(guò)數(shù)學(xué)方法鏈接在一起,因此要成功進(jìn)行加密交換,必須獲得這兩個(gè)密鑰。由于可能會(huì)影響到計(jì)算機(jī)性能,因此不對(duì)稱算法不太適用于加密大量數(shù)據(jù)。不對(duì)稱算法的常見(jiàn)用法是將對(duì)稱密鑰和初始化向量加密并傳輸給對(duì)方。然后在雙方之間來(lái)回發(fā)送的消息中使用對(duì)稱算法加密和解密數(shù)據(jù)。
如果您不打算再恢復(fù)原始值,尤其不希望別人發(fā)現(xiàn)原始值,那么請(qǐng)使用散列值。散列可以將任意長(zhǎng)度的字符串加密為固定的字節(jié)集。此操作是單向的,因此通常用于密碼這樣的少量數(shù)據(jù)。當(dāng)用戶在安全的輸入屏幕上輸入用戶密碼后,程序?qū)?duì)此密碼進(jìn)行加密并將散列值存儲(chǔ)到數(shù)據(jù)庫(kù)中。即使數(shù)據(jù)庫(kù)泄漏,也沒(méi)有人能夠讀取密碼,因?yàn)槊艽a已被加密。當(dāng)用戶登錄到該系統(tǒng)進(jìn)行輸入時(shí),將使用相同的算法解密用戶鍵入的密碼,如果兩個(gè)散列值相匹配,系統(tǒng)則可以確定用戶輸入的值與以前存儲(chǔ)的值相同。
加密練習(xí)
示例應(yīng)用程序中包括一個(gè)窗體,允許您使用 des 和 tripledes 加密服務(wù)提供程序進(jìn)行加密練習(xí)。該窗體名為 frmencrypt,如圖 3 所示。
圖 3:加密算法允許您加密并解密值。
在該屏幕上,首先需要單擊 gen key(生成密鑰)按鈕,然后單擊 gen iv(生成 iv)按鈕。然后在 original string(原始字符串)文本框中輸入一些數(shù)據(jù)并單擊 encrypt(加密)按鈕。單擊 encrypt(加密)按鈕后,加密后的文本將顯示在 encrypted string(加密字符串)文本框中。如果您希望在自己的應(yīng)用程序中使用這個(gè)加密的字符串,則需要記下生成的密鑰和 iv,因?yàn)橐饷苓B接字符串以便再次使用,您需要提供這兩個(gè)值。如果丟失了密鑰和 iv,將再也無(wú)法恢復(fù)連接字符串。
現(xiàn)在看一下其源代碼,了解如何實(shí)現(xiàn)加密和解密例程。首先看一下該類的成員變量,它用于保存相應(yīng)加密服務(wù)提供程序的引用。該成員變量的類型為 symmetricalgorithm。所有的對(duì)稱算法類都是從這個(gè)基類繼承而來(lái)的。
' visual basic .net
private mcsp as symmetricalgorithm
// c#
private symmetricalgorithm mcsp;
根據(jù)您在此窗體上選擇的選項(xiàng)按鈕,此 mcsp 變量將被指定給特定的對(duì)稱算法類。setenc() 方法將負(fù)責(zé)創(chuàng)建適當(dāng)?shù)念愋筒⑵浞祷氐讲煌姆椒ā?br>
' visual basic .net
private function setenc() as symmetricalgorithm
 if optdes.checked then
 return new descryptoserviceprovider
 else
 if opttripledes.checked then
 return new tripledescryptoserviceprovider
 end if
 end if
end function
// c#
private symmetricalgorithm setenc()
{
 if(optdes.checked)
 return new descryptoserviceprovider();
 else
 return new tripledescryptoserviceprovider();
}
如您所見(jiàn),根據(jù)您在該窗體上選擇的選項(xiàng)按鈕,將創(chuàng)建 descryptoserviceprovider 對(duì)象或 tripledescryptoserviceprovider 對(duì)象。
實(shí)現(xiàn)加密和解密的密鑰
要使用對(duì)稱算法,必須提供要使用的密鑰。每個(gè) cryptosymmetricalgorithm 實(shí)現(xiàn)都提供一種 generatekey 方法。它們實(shí)際上使用的是公共語(yǔ)言運(yùn)行時(shí) (clr) 類中內(nèi)置的隨機(jī)數(shù)生成器類。我們來(lái)看一下 gen key(生成密鑰)按鈕的 click 事件處理程序,看它如何生成要使用的隨機(jī)密鑰值。
' visual basic .net
private sub btnkeygen_click(byval sender as _
 system.object, byval e as system.eventargs) _
 handles btnkeygen.click
 mcsp = setenc()
 mcsp.generatekey()
 txtkey.text = convert.tobase64string(mcsp.key)
end sub
// c#
private void btnkeygen_click(object sender, 
 system.eventargs e)
{
 mcsp = setenc();
 mcsp.generatekey();
 txtkey.text = convert.tobase64string(mcsp.key);
}
獲取服務(wù)提供程序的特定實(shí)現(xiàn)后,只需調(diào)用 generatekey 方法來(lái)創(chuàng)建一個(gè)用于加密的新的隨機(jī)密鑰。密鑰的大小取決于用來(lái)加密的特定提供程序。例如,des 密鑰的大小為 64 位,而 tripledes 密鑰的大小為 192 位。每個(gè) symmetricalgorithm 類上都有一個(gè) keysize 屬性,它將返回用于生成密鑰的密鑰大小。
我們還需要生成初始化向量 (iv)。iv 將幫助算法生成最終加密字符串的數(shù)據(jù)塊。iv 用于開(kāi)始第一個(gè)塊的加密。如果不提供 iv,那么只要密鑰相同,在字符串之間傳遞的通用數(shù)據(jù)將保持同一種模式。因此,需要使用 iv 作為加密數(shù)據(jù)的“隨機(jī)”組件。通過(guò)這種方式,只要使用的 iv 不同,即使密鑰相同,同一個(gè)數(shù)據(jù)也會(huì)被加密成完全不同的值。下面是生成新的 iv 的 gen iv(生成 iv)按鈕的源代碼。
' visual basic .net
private sub btnivgen_click(byval sender as system.object, byval e as system.eventargs) handles btnivgen.click
 mcsp.generateiv()
 txtiv.text = convert.tobase64string(mcsp.iv)
end sub
// c#
private void btnivgen_click(object sender, 
 system.eventargs e)
{
 mcsp.generateiv();
 txtiv.text = convert.tobase64string(mcsp.iv);
}
此代碼看起來(lái)與生成密鑰的代碼非常相似。每個(gè)加密服務(wù)提供程序類上都有一個(gè) generateiv() 方法。如果未提供 iv,該方法將生成一個(gè) iv。
加密數(shù)據(jù)
獲得密鑰和初始化向量后,現(xiàn)在可以使用 key、iv 和 original string 值來(lái)創(chuàng)建原始字符串值的加密版本。單擊 encrypt(加密)按鈕將運(yùn)行以下代碼。
' visual basic .net
private sub btnencrypt_click( _
 byval sender as system.object, _
 byval e as system.eventargs) handles cmdencrypt.click
 txtencrypted.text = encryptstring(txtoriginal.text)
end sub
// c#
private void cmdencrypt_click(object sender, system.eventargs e)
{
 txtencrypted.text = encryptstring(txtoriginal.text);
}
click 事件過(guò)程將調(diào)用名為 encryptstring() 的方法,從 original string(原始字符串)文本框中接受值并對(duì)其進(jìn)行加密。然后返回該值并將其放到 encrypted string(加密字符串)文本框中。下面是 encryptstring() 方法的代碼。
' visual basic .net
private function encryptstring(byval value as string) _
 as string
 dim ct as icryptotransform
 dim ms as memorystream
 dim cs as cryptostream
 dim byt() as byte
 ct = mcsp.createencryptor(mcsp.key, mcsp.iv)
 byt = encoding.utf8.getbytes(value)
 ms = new memorystream
 cs = new cryptostream(ms, ct, cryptostreammode.write)
 cs.write(byt, 0, byt.length)
 cs.flushfinalblock()
 cs.close()
 return convert.tobase64string(ms.toarray())
end function
// c#
private string encryptstring(string value)
{
 icryptotransform ct;
 memorystream ms;
 cryptostream cs;
 byte[] byt;
 ct = mcsp.createencryptor(mcsp.key, mcsp.iv);
 byt = encoding.utf8.getbytes(value);
 ms = new memorystream();
 cs = new cryptostream(ms, ct, cryptostreammode.write);
 cs.write(byt, 0, byt.length);
 cs.flushfinalblock();
 cs.close();
 return convert.tobase64string(ms.toarray());
}
現(xiàn)在我們分開(kāi)看一下各行代碼并了解這些代碼的作用。首先是加密進(jìn)程的幾個(gè)變量。
dim ct as icryptotransform
dim ms as memorystream
dim cs as cryptostream
dim byt() as byte
icryptotransform 是一個(gè)接口。需要此接口才能在任何服務(wù)提供程序上調(diào)用 createencryptor 方法,服務(wù)提供程序?qū)⒎祷囟x該接口的實(shí)際 encryptor 對(duì)象。
然后需要將原始字符串轉(zhuǎn)換成字節(jié)數(shù)組。大多數(shù) .net 加密算法處理的是字節(jié)數(shù)組而不是字符串。
byt = encoding.utf8.getbytes(value)
現(xiàn)在可以執(zhí)行實(shí)際的加密了。此進(jìn)程需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)流,用于將加密的字節(jié)寫入到其中。要使用名為 ms 的 memorystream 對(duì)象、icryptotransform 對(duì)象(提供給 cryptostream 類的構(gòu)造函數(shù))以及說(shuō)明您希望在何種模式(讀、寫等)下創(chuàng)建該類的枚舉常數(shù)。創(chuàng)建 cryptostream 對(duì)象 cs 后,現(xiàn)在使用 cryptostream 對(duì)象的 write 方法將數(shù)據(jù)寫入到內(nèi)存數(shù)據(jù)流。這就是進(jìn)行實(shí)際加密的方法,加密每個(gè)數(shù)據(jù)塊時(shí),數(shù)據(jù)將被寫入 memorystream 對(duì)象。
ms = new memorystream
cs = new cryptostream(ms, ct, cryptostreammode.write)
cs.write(byt, 0, byt.length)
cs.flushfinalblock()
cs.close()
創(chuàng)建 memorystream 后,該代碼將在 cryptostream 對(duì)象上執(zhí)行 flushfinalblock 方法,以確保所有數(shù)據(jù)均被寫入 memorystream 對(duì)象。該過(guò)程將關(guān)閉 cryptostream 對(duì)象。
最后,該過(guò)程將內(nèi)存數(shù)據(jù)流從字節(jié)數(shù)組轉(zhuǎn)換回字符串,這樣才可以在窗體上的文本框內(nèi)顯示該字符串。可以使用 memorystream toarray() 方法從數(shù)據(jù)流中獲取字節(jié)數(shù)組,然后調(diào)用 convert.tobase64string() 方法,該方法接受字節(jié)數(shù)組輸入并使用 base64 編碼方法將該字符串編碼為可讀內(nèi)容。
解密數(shù)據(jù)
加密數(shù)據(jù)后,有時(shí)還需要解密數(shù)據(jù)。解密數(shù)據(jù)的過(guò)程非常簡(jiǎn)單,與加密過(guò)程相似。您需要提供加密過(guò)程中使用的密鑰和初始化向量。symmetricalgorithm 類的 key 和 iv 屬性被定義為字節(jié)數(shù)組。因此,設(shè)置這些屬性之前需要提供您創(chuàng)建的字符串并將其轉(zhuǎn)換成字節(jié)數(shù)組。下面我們看一下窗體內(nèi)用于解密字符串的 decryptstring 方法。該方法是從窗體上 decrypt(解密)按鈕的 click 事件處理程序中調(diào)用的。
' visual basic .net
private function decryptstring(byval value as string) _
 as string
 dim ct as icryptotransform
 dim ms as memorystream
 dim cs as cryptostream
 dim byt() as byte
 ct = mcsp.createdecryptor(mcsp.key, mcsp.iv)
 byt = convert.frombase64string(value)
 ms = new memorystream
 cs = new cryptostream(ms, ct, cryptostreammode.write)
 cs.write(byt, 0, byt.length)
 cs.flushfinalblock()
 cs.close()
 return encoding.utf8.getstring(ms.toarray())
end function
// c#
private string decryptstring(string value)
{
 icryptotransform ct;
 memorystream ms;
 cryptostream cs;
 byte[] byt;
 ct = mcsp.createdecryptor(mcsp.key, mcsp.iv);
 byt = convert.frombase64string(value);
 ms = new memorystream();
 cs = new cryptostream(ms, ct, cryptostreammode.write);
 cs.write(byt, 0, byt.length);
 cs.flushfinalblock();
 cs.close();
 return encoding.utf8.getstring(ms.toarray());
}
encrypt 函數(shù)和 decrypt 函數(shù)只有三個(gè)不同之處。 
需要使用 cryptoserviceprovider 類的 createdecryptor 方法來(lái)創(chuàng)建相應(yīng)的 ictryptotransform 對(duì)象。 
需要將 base64 編碼字符串轉(zhuǎn)換成字節(jié)數(shù)組。需要使用 convert.frombase64string 方法來(lái)實(shí)現(xiàn)此轉(zhuǎn)換。 
通過(guò)對(duì)原始字節(jié)數(shù)組進(jìn)行轉(zhuǎn)換,將字節(jié)數(shù)組轉(zhuǎn)換成相應(yīng)的內(nèi)存數(shù)據(jù)流。需要將內(nèi)存數(shù)據(jù)流從字節(jié)數(shù)組轉(zhuǎn)換回可以在窗體上再次顯示的普通字符串。需要使用 encoding.utf8.getstring() 方法來(lái)實(shí)現(xiàn)此轉(zhuǎn)換。 
注意:encoding.utf8 類來(lái)自于 system.text 命名空間。
是否可以使其更簡(jiǎn)單?!
盡管到目前為止顯示的代碼并不難,但有很多不同的類和接口您可能還不習(xí)慣于使用。此外,還要記住很多代碼。下面我們學(xué)習(xí)如何將 cryptography 類包裝成易于使用的類。
名為 pdsacryptography 的程序集中有兩個(gè)類,分別為 pdsahash 和 pdsaencryption。這兩個(gè)類用于封裝創(chuàng)建散列字符串或加密字符串的實(shí)際機(jī)制。此外,它們還允許您使用枚舉常數(shù)來(lái)決定要使用哪種散列或加密算法。不必記住每個(gè)不同的加密服務(wù)提供程序的所有不同的名稱,即可獲得不錯(cuò)的 intellisense® 提供程序列表。
使用 pdsahash 包裝散列
pdsahash 類包含屬性 hashtype、hashobject、originalstring、hashstring、saltvalue、usesalt 和 saltlength。與該類相關(guān)的方法包括 setencryptor、createsalt、reset 和 createhash。該類中創(chuàng)建了一個(gè)稱為 pdsahashtype 的枚舉,您可以從中選擇要使用的相應(yīng)散列類。該類的作用是將上文所示的代碼簡(jiǎn)化為以下代碼。
private sub usepdsahash()
 dim ph as new pdsahash(pdsahash.pdsahashtype.md5)
 messagebox.show(ph.createhash("paul"))
end sub
我寧愿鍵入以上代碼也不愿意記住上文顯示的所有代碼。如您所見(jiàn),這段代碼相當(dāng)簡(jiǎn)單,與上文所述的代碼完全相同,只是被包裝到一個(gè)易于使用的接口中。您可以從本文包含的示例中找到完整的類。下面列舉了可以使用該類創(chuàng)建的不同散列算法。
public enum pdsahashtype as byte
 md5
 sha1
 sha256
 sha384
 sha512
end enum
其中的每個(gè)算法都為最終的散列提供了一個(gè)不同的安全級(jí)別。.net 中完整的散列類列表如下所示: 
md5cryptoserviceprovider 
sha1cryptoserviceprovider 
sha256managed 
sha384managed 
sha512managed 
有關(guān)這些不同的散列類型的詳細(xì)信息,請(qǐng)參閱 visual studio .net 聯(lián)機(jī)文檔。
使用 pdsaencryption 包裝加密
就像 pdsahash 類可以包裝 .net framework 中的所有散列功能一樣,pdsaencryption 類可用于包裝 .net framework 中的各種對(duì)稱算法類。pdsaencryption 類包括以下枚舉類型,允許您創(chuàng)建各種加密/解密對(duì)象。
public enum pdsaencryptiontype as byte
 des
 rc2
 rijndael
 tripledes
end enum
同樣,每個(gè)服務(wù)提供程序都為加密字符串提供了不同的安全級(jí)別。這在 visual studio .net 聯(lián)機(jī)文檔中都有詳細(xì)的介紹,這里不再贅述。
該類包含屬性 encryptiontype、originalstring、encryptedstring、key、keystring、iv、ivstring 和 cryptoprovider。其中的大多數(shù)屬性都是不言自明的,但 key 和 iv 屬于字節(jié)數(shù)組,而 keystring 和 ivstring 是這些字節(jié)數(shù)組的字符串表示。
該類還包含一些方法。例如 encrypt 和 decrypt。還有 generatekey 和 generateiv 方法,如果沒(méi)有現(xiàn)成的密鑰和 iv,可以使用這兩個(gè)方法創(chuàng)建一個(gè)密鑰和 iv。另外還有 setencryptor 方法,它用于創(chuàng)建將在各種方法中使用的新的 cryptoprovider 對(duì)象。
該類的作用是使加密和解密字符串更容易實(shí)現(xiàn)。例如,下面的代碼片斷顯示了使用該類加密字符串是多么容易。
private sub btnhardcoded_click( _
 byval sender as system.object, _
 byval e as system.eventargs) handles btnhardcoded.click
 dim ps as new pdsasymmetric( _ 
 pdsasymmetric.pdsaencryptiontype.tripledes)
 messagebox.show(ps.encrypt( _
 "server=localhost;database=northwind;uid=sa;pwd=sa"))
end sub
小結(jié)
使用 microsoft .net framework 中的類可以很容易地在計(jì)算機(jī)中保存機(jī)密信息。您會(huì)發(fā)現(xiàn) cryptography 命名空間中的多個(gè)類都可以很好完成這一任務(wù)。為這些類創(chuàng)建包裝可以幫助您大大減少需要編寫的代碼量。強(qiáng)烈建議您按照本文所述,創(chuàng)建類似的包裝。