打造屬于你的加密Helper類
摘要
在我們軟件系統(tǒng)設(shè)計中,數(shù)據(jù)的安全性是我們考慮的重中之重,特別像銀行系統(tǒng)的設(shè)計賬戶和密碼都需進(jìn)行加密處理。這時我們可以使用加密算法對數(shù)據(jù)進(jìn)行加密處理,這就是我們今天要介紹的主題。
首先讓我們了解加密算法分為:對稱、非對稱加密算法和Hash加密。
對稱加密算法:首先需要發(fā)送方和接收方協(xié)定一個密鑰K。K可以是一個密鑰對,但是必須要求加密密鑰和解密密鑰之間能夠互相推算出來。在最簡單也是最常用的對稱算法中,加密和解密共享一個密鑰。
非對稱加密算法:首先得有一個密鑰對,這個密鑰對含有兩部分內(nèi)容,分別稱作公鑰(PK)和私鑰(SK),公鑰通常用來加密,私鑰則用來解密。在對稱算法中,也講到了可以有兩個密鑰(分為加密和解密密鑰)。但是,對稱算法中的加解密密鑰可以互相轉(zhuǎn)換,而在非對稱算法中,則不能從公鑰推算出私鑰,所以我們完全可以將公鑰公開到任何地方。
正面
圖1 .NET中對稱加密算法
圖2 .NET中非對稱加密算法
通過上面.NET的對稱和非對稱加密算法類結(jié)構(gòu)圖,我們可以發(fā)現(xiàn)在.NET Framework中通過提供者模式實現(xiàn)加密算法動態(tài)擴(kuò)展,SymmetricAlgorithm和AsymmetricAlgorithm類分別為對稱和非對稱算法的基類,接著通過擴(kuò)展不同的算法類型(如:DES,TripleDES等),來動態(tài)地擴(kuò)展不同類型的算法,最后是每種算法的具體實現(xiàn)Provider(如:DESCryptoServiceProvider,TripleDESCryptoServiceProvider等)。
現(xiàn)在我們對.NET Framework中的加密算法有了初步了解,接下來讓我們通過打造屬于我們的加密Helper類,來加深加密算法的使用。
對稱加密算法
首先讓我們實現(xiàn)對稱算法的加密和解密方法,先定義對稱算法加密方法Encypt(),它提供所有對稱算法加密算法的加密實現(xiàn)。
/// <summary> /// Encrypts the specified algorithm. /// The type of algorithm is SymmetricAlgorithm, so the Encrypt /// supports all kind of Symmetric Algorithm (cf: DES, TripleDes etc). /// </summary> /// /// <param name="algorithm">The algorithm.</param> /// <param name="plainText">The plain text.</param> /// <param name="key">The key.</param> /// <param name="cipherMode">The cipher mode.</param> /// <param name="paddingMode">The padding mode.</param> /// <returns> The string base on base64. </returns> public static string Encrypt(SymmetricAlgorithm algorithm, string plainText, byte[] key, CipherMode cipherMode, PaddingMode paddingMode) { byte[] plainBytes; byte[] cipherBytes; algorithm.Key = key; algorithm.Mode = cipherMode; algorithm.Padding = paddingMode; BinaryFormatter bf = new BinaryFormatter(); using (MemoryStream stream = new MemoryStream()) { bf.Serialize(stream, plainText); plainBytes = stream.ToArray(); } using (MemoryStream ms = new MemoryStream()) { // Defines a stream for cryptographic transformations CryptoStream cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write); // Writes a sequence of bytes for encrption cs.Write(plainBytes, 0, plainBytes.Length); // Closes the current stream and releases any resources cs.Close(); // Save the ciphered message into one byte array cipherBytes = ms.ToArray(); // Closes the memorystream object ms.Close(); } string base64Text = Convert.ToBase64String(cipherBytes); return base64Text; }
現(xiàn)在我們完成了對稱加密算法的加密方法Encrypt(),它的簽名包含一個SymmetricAlgorithm類參數(shù),前面提到對稱加密算法都是繼承于SymmetricAlgorithm抽象類,所用我們通過多態(tài)性降低了Encrypt()方法和不同的加密算法類之間的耦合度。
接著傳遞的參數(shù)是要加密的明文plainText、密鑰key,使用的加密模式cipherMode和加密數(shù)據(jù)的填充方式paddingMode。最后返回一組由base64格式組成的加密數(shù)據(jù)。
上面我們已經(jīng)完成了對稱加密算法的加密方法Encrypt(),接著讓我們實現(xiàn)對稱算法的解密方法,首先定義對稱算法解密方法Decrypt(),它提供所有對稱算法解密算法的解密實現(xiàn)。
/// <summary> /// Decrypts the specified algorithm. /// </summary> /// <param name="algorithm">The algorithm.</param> /// <param name="base64Text">The base64 text.</param> /// <param name="key">The key.</param> /// <param name="cipherMode">The cipher mode.</param> /// <param name="paddingMode">The padding mode.</param> /// <returns> The recovered text. </returns> public static string Decrypt(SymmetricAlgorithm algorithm, string base64Text, byte[] key, CipherMode cipherMode, PaddingMode paddingMode) { byte[] plainBytes; //// Convert the base64 string to byte array. byte[] cipherBytes = Convert.FromBase64String(base64Text); algorithm.Key = key; algorithm.Mode = cipherMode; algorithm.Padding = paddingMode; using (MemoryStream memoryStream = new MemoryStream(cipherBytes)) { using (CryptoStream cs = new CryptoStream(memoryStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read)) { plainBytes = new byte[cipherBytes.Length]; cs.Read(plainBytes, 0, cipherBytes.Length); } } string recoveredMessage; using (MemoryStream stream = new MemoryStream(plainBytes, false)) { BinaryFormatter bf = new BinaryFormatter(); recoveredMessage = bf.Deserialize(stream).ToString(); } return recoveredMessage; }
我們已經(jīng)完成了對稱加密算法的解密方法Decrypt(),它的簽名形式和加密方法Encrypt()基本一致,只是我們傳遞要解密的密文進(jìn)行解密就OK了。
現(xiàn)在讓我們回憶一下對稱加密算法的定義,其中有幾點我們要注意的是,加密和解密密鑰可以相同,或通過一定算法由加密密鑰推演出解密密鑰,所以我們?yōu)榱舜_保數(shù)據(jù)的安全性最好設(shè)置對稱算法初始化向量IV,其目的是在初始加密數(shù)據(jù)頭部添加其他數(shù)據(jù),從而降低密鑰被破譯的幾率。
初始化向量IV在加密算法中起到的也是增強(qiáng)破解難度的作用。在加密過程中,如果遇到相同的數(shù)據(jù)塊,其加密出來的結(jié)果也一致,相對就會容易破解。加密算法在加密數(shù)據(jù)塊的時候,往往會同時使用密碼和上一個數(shù)據(jù)塊的加密結(jié)果。因為要加密的第一個數(shù)據(jù)塊顯然不存在上一個數(shù)據(jù)塊,所以這個初始化向量就是被設(shè)計用來當(dāng)作初始數(shù)據(jù)塊的加密結(jié)果。
現(xiàn)在我們已經(jīng)完成了對稱加密算法的加密和解密方法,接下來我們繼續(xù)完成非對稱加密算法的加密和解密方法吧!
非對稱加密算法
在.NET Framework中提供四種非對稱加密算法(DSA,ECDiffieHellman, ECDsa和RSA),它們都繼承于抽象類AsymmetricAlgorithm。但這里我們只提供RSA的加密和解密方法的實現(xiàn)。
首先我們定義一個方法GenerateRSAKey() 來生成RSA的公鑰和密鑰,它的簽名包含一個RSACryptoServiceProvider類型的參數(shù),細(xì)心的大家肯定會發(fā)現(xiàn)為什么我們沒有把參數(shù)類型定義為AsymmetricAlgorithm,這不是可以充分的利用抽象類的特性嗎?當(dāng)我們查看抽象類AsymmetricAlgorithm發(fā)現(xiàn),它并沒有定義公鑰和密鑰的生成方法——ToXmlString()方法,這個方法是在RSA類中定義的,所以這里使用RSACryptoServiceProvider類型。
/// <summary> /// Generates the RSA key. /// </summary> /// <param name="algorithm">The algorithm.</param> /// <returns></returns> public static void GenerateRSAKey(RSACryptoServiceProvider algorithm) { RSAPrivateKey = algorithm.ToXmlString(true); using (StreamWriter streamWriter = new StreamWriter("PublicPrivateKey.xml")) { streamWriter.Write(RSAPrivateKey); } RSAPubicKey = algorithm.ToXmlString(false); using (StreamWriter streamWriter = new StreamWriter("PublicOnlyKey.xml")) { streamWriter.Write(RSAPubicKey); } }
我們通過ToXmlString()方法生成公鑰和密鑰,然后把公鑰和密鑰寫到流文件中,當(dāng)我們要使用公鑰和密鑰時,可以通過讀流文件把公鑰和密鑰讀取出來。
OK接下來讓我們完成RSA的加密和解密方法吧!
/// <summary> /// Encrypts the specified algorithm. /// The Asymmetric Algorithm includes DSA,ECDiffieHellman, ECDsa and RSA. /// Code Logic: /// 1. Input encrypt algorithm and plain text. /// 2. Read the public key from stream. /// 3. Serialize plian text to byte array. /// 4. Encrypt the plian text array by public key. /// 5. Return ciphered string. /// </summary> /// <param name="algorithm">The algorithm.</param> /// <param name="plainText">The plain text.</param> /// <returns></returns> public static string Encrypt(RSACryptoServiceProvider algorithm, string plainText) { string publicKey; List<Byte[]> cipherArray = new List<byte[]>(); //// read the public key. using (StreamReader streamReader = new StreamReader("PublicOnlyKey.xml")) { publicKey = streamReader.ReadToEnd(); } algorithm.FromXmlString(publicKey); BinaryFormatter binaryFormatter = new BinaryFormatter(); byte[] plainBytes = null; //// Use BinaryFormatter to serialize plain text. using (MemoryStream memoryStream = new MemoryStream()) { binaryFormatter.Serialize(memoryStream, plainText); plainBytes = memoryStream.ToArray(); } int totLength = 0; int index = 0; //// Encrypt plain text by public key. if (plainBytes.Length > 80) { byte[] partPlainBytes; byte[] cipherBytes; myArray = new List<byte[]>(); while (plainBytes.Length - index > 0) { partPlainBytes = plainBytes.Length - index > 80 ? new byte[80] : new byte[plainBytes.Length - index]; for (int i = 0; i < 80 && (i + index) < plainBytes.Length; i++) partPlainBytes[i] = plainBytes[i + index]; myArray.Add(partPlainBytes); cipherBytes = algorithm.Encrypt(partPlainBytes, false); totLength += cipherBytes.Length; index += 80; cipherArray.Add(cipherBytes); } } //// Convert to byte array. byte[] cipheredPlaintText = new byte[totLength]; index = 0; foreach (byte[] item in cipherArray) { for (int i = 0; i < item.Length; i++) { cipheredPlaintText[i + index] = item[i]; } index += item.Length; } return Convert.ToBase64String(cipheredPlaintText); }
上面給出了RSA的加密方法實現(xiàn),首先該方法傳遞一個RSACryptoServiceProvider類型的參數(shù)和明文,然后我們從流中讀取預(yù)先已經(jīng)創(chuàng)建的公鑰,接著把明文序列化為Byte數(shù)組,而且使用公鑰對明文進(jìn)行加密處理,最后把加密的Byte數(shù)組轉(zhuǎn)換為base64格式的字符串返回。
OK現(xiàn)在讓我們完成RSA的解密方法Decrypt(),其實原理和前面的加密方法基本一致,只是我們要使用密鑰對密文進(jìn)行解密。
/// <summary> /// Decrypts the specified algorithm. /// </summary> /// <param name="algorithm">The algorithm.</param> /// <param name="base64Text">The base64 text.</param> /// <returns></returns> public static string Decrypt(RSACryptoServiceProvider algorithm, string base64Text) { byte[] cipherBytes = Convert.FromBase64String(base64Text); List<byte[]> plainArray = new List<byte[]>(); string privateKey = string.Empty; //// Read the private key. using (StreamReader streamReader = new StreamReader("PublicPrivateKey.xml")) { privateKey = streamReader.ReadToEnd(); } algorithm.FromXmlString(privateKey); int index = 0; int totLength = 0; byte[] partPlainText = null; byte[] plainBytes; int length = cipherBytes.Length / 2; //int j = 0; //// Decrypt the ciphered text through private key. while (cipherBytes.Length - index > 0) { partPlainText = cipherBytes.Length - index > 128 ? new byte[128] : new byte[cipherBytes.Length - index]; for (int i = 0; i < 128 && (i + index) < cipherBytes.Length; i++) partPlainText[i] = cipherBytes[i + index]; plainBytes = algorithm.Decrypt(partPlainText, false); totLength += plainBytes.Length; index += 128; plainArray.Add(plainBytes); } byte[] recoveredPlaintext = new byte[length]; List<byte[]> recoveredArray; index = 0; for (int i = 0; i < plainArray.Count; i++) { for (int j = 0; j < plainArray[i].Length; j++) { recoveredPlaintext[index + j] = plainArray[i][j]; } index += plainArray[i].Length; } BinaryFormatter bf = new BinaryFormatter(); using (MemoryStream stream = new MemoryStream()) { stream.Write(recoveredPlaintext, 0, recoveredPlaintext.Length); stream.Position = 0; string msgobj = (string)bf.Deserialize(stream); return msgobj; } }
我們很快就實現(xiàn)RSA的解密方法實現(xiàn),代碼邏輯和加密算法基本一致,我們只需注意把明文換成密文,公鑰換成密鑰就基本不會出錯了。
Hash加密
Hash算法特別的地方在于它是一種單向算法,用戶可以通過Hash算法對目標(biāo)信息生成一段特定長度的唯一的Hash值,卻不能通過這個Hash值重新獲得目標(biāo)信息,因此Hash算法常用在不可還原的密碼存儲、信息完整性校驗等,所用我們代碼邏輯就沒有解密方法,而是判斷加密的密文是否匹配。
/// <summary> /// Encrypts the specified hash algorithm. /// 1. Generates a cryptographic Hash Key for the provided text data. /// </summary> /// <param name="hashAlgorithm">The hash algorithm.</param> /// <param name="dataToHash">The data to hash.</param> /// <returns></returns> public static string Encrypt(HashAlgorithm hashAlgorithm, string dataToHash) { string[] tabStringHex = new string[16]; UTF8Encoding UTF8 = new System.Text.UTF8Encoding(); byte[] data = UTF8.GetBytes(dataToHash); byte[] result = hashAlgorithm.ComputeHash(data); StringBuilder hexResult = new StringBuilder(result.Length); for (int i = 0; i < result.Length; i++) { //// Convert to hexadecimal hexResult.Append(result[i].ToString("X2")); } return hexResult.ToString(); }
接著我們定義IsHashMatch()方法判斷加密的密文是否一致。
/// <summary> /// Determines whether [is hash match] [the specified hash algorithm]. /// </summary> /// <param name="hashAlgorithm">The hash algorithm.</param> /// <param name="hashedText">The hashed text.</param> /// <param name="unhashedText">The unhashed text.</param> /// <returns> /// <c>true</c> if [is hash match] [the specified hash algorithm]; otherwise, <c>false</c>. /// </returns> public static bool IsHashMatch(HashAlgorithm hashAlgorithm, string hashedText, string unhashedText) { string hashedTextToCompare = Encrypt(hashAlgorithm, unhashedText); return (String.Compare(hashedText, hashedTextToCompare, false) == 0); }
現(xiàn)在我們已經(jīng)實現(xiàn)了屬于我們的加密Helper類了,現(xiàn)在讓我們測試一下自定義Helper類是否好用,我們可以通過Unit Test或一個界面程序來測試我們的Helper類的功能是否完善,這里我們選擇使用一個界面程序來說話。
首先我們創(chuàng)建一個應(yīng)用程序界面,然后我們添加兩個Tabpage分別對應(yīng)對稱和非對稱加密程序。
圖3加密程序界面
圖4非對稱加密程序界面
圖5對稱加密程序界面
現(xiàn)在我們已經(jīng)把界面設(shè)計好了,接下來讓我們完成界面邏輯的設(shè)計。
/// <summary> /// Handles the Click event of the btnEncrypt control. /// Encypt plain text by symmetric algorithm. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> private void btnEncrypt_Click(object sender, EventArgs e) { if (String.IsNullOrEmpty(txtInputMsg.Text) || String.IsNullOrEmpty(txtRandomInit.Text) || String.IsNullOrEmpty(txtRandomKey.Text)) { MessageBox.Show("Invalid parameters!"); return; } if (_symmetricAlgorithm != null) { try { //// Specified IV. _symmetricAlgorithm.IV = _initVector; //// Invoke Cryptography helper. /// Get ciphered text. txtCipherMsg.Text = CryptographyUtils.Encrypt(_symmetricAlgorithm, txtInputMsg.Text.Trim(), _key, _cipherMode, _paddingMode); //// Convert string to byte array. _cipherBytes = Convert.FromBase64String(txtCipherMsg.Text); //// Initialize text box. InitTextBox(_cipherBytes, ref txtCipherByte); } catch (Exception ex) { MessageBox.Show(ex.Message); } } }
接著我們同樣通過調(diào)用Helper類的Decrypt()方法,使用對稱算法對數(shù)據(jù)進(jìn)行解密處理就OK了。
/// <summary> /// Handles the Click event of the btnDecrypt control. /// Decrypt plain text by symmetric algorithm /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> private void btnDecrypt_Click(object sender, EventArgs e) { if (_symmetricAlgorithm != null) { try { //// Specified IV. _symmetricAlgorithm.IV = _initVector; //// Decrypt ciphered text. txtOutputMsg.Text = CryptographyUtils.Decrypt(_symmetricAlgorithm, txtCipherMsg.Text.Trim(), _key, _cipherMode, _paddingMode); } catch (Exception ex) { MessageBox.Show(ex.Message); } } }
接著我們同樣通過調(diào)用Helper類的Decrypt()方法,使用對稱算法對數(shù)據(jù)進(jìn)行解密處理就OK了。
圖6對稱加密程序效果
圖7非對稱加密程序效果
總結(jié)
通過本文的介紹相信大家對.NET Framework中提供的加密算法有了初步的了解。
最后,我們在實際應(yīng)用中對于大量數(shù)據(jù)進(jìn)行加密時應(yīng)該采用對稱加密算法,對稱加密算法的優(yōu)點在于加解密的高速度和使用長密鑰時的難破解性。而非對稱加密的缺點是加解密速度要遠(yuǎn)遠(yuǎn)慢于對稱加密,非對稱加密算法適合于數(shù)據(jù)量少的情況,應(yīng)該始終考慮使用對稱加密的方式進(jìn)行文件的加解密工作。當(dāng)然,如果文件加密后要傳給網(wǎng)絡(luò)中的其它接收者,而接收者始終要對文件進(jìn)行解密的,這意味著密鑰也是始終要傳送給接收者的。這個時候,非對稱加密就可以派上用場了,它可以用于字符串的加解密及安全傳輸場景。
|
|
關(guān)于作者:[作者]:
JK_Rush從事.NET開發(fā)和熱衷于開源高性能系統(tǒng)設(shè)計,通過博文交流和分享經(jīng)驗,歡迎轉(zhuǎn)載,請保留原文地址,謝謝。 |

摘要 在我們軟件系統(tǒng)設(shè)計中,數(shù)據(jù)的安全性是我們考慮的重中之重,特別像銀行系統(tǒng)的設(shè)計賬戶和密碼都需進(jìn)行加密處理。這時我們可以使用加密算法對數(shù)據(jù)進(jìn)行加密處理,這就是我們今天要介紹的主題。 首先讓我們...







浙公網(wǎng)安備 33010602011771號