上次我已經設計出了基本的介面,而且也確定了使用ini檔案做為參數的儲存方式,調用Windows API函數進行INI檔案的讀寫操作,但有一個重要的問題,這個小程式涉及到了二個方面的密碼:一是資料庫的串連密碼,二是郵件伺服器的發送密碼,如果我們直接把密碼以明文的方式儲存到INI檔案中,如果一不小心INI檔案泄露的話,其他人很容易就可以得到這些參數,可能會對資料庫或郵件伺服器進行攻擊或者偽造郵件,造成不必要的麻煩。因此,我下一步考慮的是如何對敏感資訊進行加密。
現在通用的密碼編譯演算法有很多,但首先要確定的一點就是,我們選擇的密碼編譯演算法一定要是可逆的,也就是說,使用同一個密碼,可以加密,也可以解密出明文,否則就無法將儲存的密碼恢複,這樣的話,最常用的MD5加密方式就不能使用了,我選擇的是DES加密方式,雖然這種加密方式可能已經有點老了,但用在這個場合感覺已經夠用了,最重要的是,它本身就包括在了vs的一個類中,很方便就可以調用。
Imports System.Security.CryptographyImports System.TextPublic Function Encrypt(ByVal pToEncrypt As String, ByVal sKey As String) As String Dim des As New DESCryptoServiceProvider() Dim inputByteArray() As Byte inputByteArray = Encoding.Default.GetBytes(pToEncrypt) ''建立加密對象的密鑰和位移量 ''原文使用ASCIIEncoding.ASCII方法的GetBytes方法 ''使得輸入密碼必須輸入英文文本 des.Key = ASCIIEncoding.ASCII.GetBytes(sKey) des.IV = ASCIIEncoding.ASCII.GetBytes(sKey) ''寫位元組到加密流 ''(把記憶體流中的內容全部寫入) Dim ms As New System.IO.MemoryStream() Dim cs As New CryptoStream(ms, des.CreateEncryptor, CryptoStreamMode.Write) ''寫位元組到加密流 ''(把記憶體流中的內容全部寫入) cs.Write(inputByteArray, 0, inputByteArray.Length) cs.FlushFinalBlock() ''建立輸出字串 Dim ret As New StringBuilder() Dim b As Byte For Each b In ms.ToArray() ret.AppendFormat("{0:X2}", b) Next Return ret.ToString() End Function Public Function Decrypt(ByVal pToDecrypt As String, ByVal sKey As String) As String Try Dim des As New DESCryptoServiceProvider() ''把字串放入byte數組 Dim len As Integer len = pToDecrypt.Length / 2 - 1 Dim inputByteArray(len) As Byte Dim x, i As Integer For x = 0 To len i = Convert.ToInt32(pToDecrypt.Substring(x * 2, 2), 16) inputByteArray(x) = CType(i, Byte) Next ''建立加密對象的密鑰和位移量,此值重要,不能修改 des.Key = ASCIIEncoding.ASCII.GetBytes(sKey) des.IV = ASCIIEncoding.ASCII.GetBytes(sKey) Dim ms As New System.IO.MemoryStream() Dim cs As New CryptoStream(ms, des.CreateDecryptor, CryptoStreamMode.Write) cs.Write(inputByteArray, 0, inputByteArray.Length) cs.FlushFinalBlock() Return Encoding.Default.GetString(ms.ToArray) Catch ex As Exception Return "" End Try End Function
要注意的是,DES演算法的密鑰必須是八位英文字母或數字,不能多也不能少,不然就會報錯。
現在,我將讀寫INI的語句稍做改變,寫入時加上密碼,讀取時自動解密,假設密鑰使用一個固定的八位字串,範例程式碼如下:
IniFile = Application.StartupPath + "\sendmail.ini"'將密碼加密後寫入INI檔案,密鑰固定為"vvian999"WriteINI("SMTP", "password", Encrypt(Txt_MailPwd.Text, "vvian999"), IniFile)'將讀出的密碼解密後放入文字框內Txt_MailPwd.Text = Decrypt(GetINI("SMTP", "password", "", IniFile), "vvian999")
這樣一來,存入INI檔案的密碼參數就已經是加密後的字串,在不知道密鑰的情況下,一般人已經很難將其破解,得到敏感資訊了。但安全永遠是放在第一位的,我又想到,如果得到INI檔案的那個人在其他的機器上運行了這個程式,文字框中就會顯示出星號的密碼明文,只要使用一個密碼查看器之類的軟體,密碼也會泄露了,而且密鑰如果寫死在可執行檔中,加密的規律是相同的,也不好呀,最好的辦法是每台機器的密鑰是一個固定的值,但各台機器的密鑰又不相同,這樣的話,就算把程式和INI檔案一起拷到其他伺服器上也無法正常執行,安全也有了保障。
每台機器一個固定密鑰最好的辦法就是擷取該機器的某個硬體值,如硬碟序號、CPU編號、網卡MAC地址等一般不會變化的資料,使用這些資料得到一個八位長度的字串,以此為密鑰,加密一個只有自己才知道的字串,每次啟動時解密一次,如果解密結果相同,就繼續進行,如果解密結果不同,就報錯,要求重新輸入資料庫和郵件伺服器的密碼,最大程度的保證了資料的安全,這實際上也是共用軟體常用的註冊碼方式吧。
擷取硬體資訊我使用的是System.Management,使用wmi來擷取,比如下面的例子就是擷取網卡的MAC地址,然後取前八位做為密鑰進行加密,因為MAC地址都是12位的,不用擔心長度不夠的問題,也可以取後八位、中間八位等方式來處理。加一個加密好的字串同時放入INI檔案後,以後每次啟動程式時進行一次檢查。
'先擷取硬體代碼,這裡使用的是網卡MAC地址的前八位Dim HwCode as StringDim macaddr As StringDim Wmimac As New System.Management.ManagementObjectSearcher("SELECT * FROM Win32_NetworkAdapterConfiguration")For Each WmiObj As ManagementObject In Wmimac.Get If CBool(WmiObj("IPEnabled")) Then macaddr = WmiObj("MACAddress") End IfNextHwCode = Microsoft.VisualBasic.Left(Replace(macaddr, ":", ""),8))'把驗證字串寫入INI檔案WriteINI("SendMail", "CRC", Encrypt("clicvvian2010", HwCode), IniFile)
還有一點,如果是伺服器上一般不止一塊網址,我使用的是最後一塊網卡的地址,你也可以使用其他方式來指定某一塊網卡,使用wmi來擷取其他硬體資訊的方法,大家可以通過google搜尋,這裡就不多說了。
Private Sub sendmail_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load'前面的一些代碼已經刪除,只留下判斷過程的 If Decrypt(GetINI("SendMail", "CRC", "", IniFile), HwCode) <> "clicvvian2010" Then MessageBox.Show("注意:檢測到伺服器有變化,請重新輸入資料庫、郵件伺服器密碼後儲存,現密碼已解碼出錯!", "注意", MessageBoxButtons.OK, MessageBoxIcon.Information) End If' End Sub
處理了一個問題後,對於第二個問題也可以開始考慮,就是將解密後的密碼放迴文本框後,如果有心人可以接觸到伺服器,一樣可以通過密碼查看軟體得到密碼,因此我的處理方式是將讀入的密碼密文直接放到文字框中,將解密後的密碼明文放到一個Public變數中,在要使用密碼的地方,先進行一次判斷,將變數中的密碼加密後與文字框中的對比,如果一致,就使用變數中的密碼,如果不一致,說明使用者修改過密碼,就將文字框中的密碼符值給變數然後使用,當使用者每點擊一次“儲存參數”時,再將加密後的密碼重新放到密碼框中。當然這也有問題,就是使用者修改了密碼,又沒有點擊儲存參數時,密碼還是以明文方式存放在文字框中,我嘗試使用了Txt_MailPwd_LostFocus和Txt_MailPwd_Validated二種方式都沒能做過修改後即時變換,不知道各位有沒有什麼好的辦法。
與上次相比,介面做了一點點改動,增加了一個測試郵件的輸入框和發送測試郵件的按鈕,同時去掉了“資料庫優先”選項,就直接預設為資料庫的資訊優先吧(指的是如果資料庫中的某條記錄指定了寄件者姓名,就不再使用本處輸入的內容),比上次的排列更好看了一些,呵呵
原打算在這次就進入資料庫設計的,看來又要推到下一次了,呵呵,我力爭加大進度,在國慶長假期間完成這個小程式吧。