加密解密概述及.NET中對加密解密的支援(二)
.NET中加密解密的支援
相信通過前面幾頁的敘述,大家已經明白了加密解密、數字簽名的基本原理,下面我們看一下在.NET中是如何來支援加密解密的。正如上面我們所進行的分類,.NET中也提供了兩組類用於加密解密,一組為對稱加密,一組為非對稱加密。這些類按照名稱還可以分為兩組,一組字尾為“CryptoServiceProvider”的,是對於底層Windows API的包裝類,一組字尾為“Managed”,是在.NET中全新編寫的類。
.NET對稱加密解密支援
.NET Framework提供了一些常見的對稱演算法的實現,包括DES、RC2等。下面列出了與對稱演算法有關的類的結構:
System.Security.Cryptography.SymmetricAlgorithm
System.Security.Cryptography.DESCryptoServiceProvider
System.Security.Cryptography.RC2
System.Security.Cryptography.TripleDESCryptoServiceProvider
這些類中黑體表示的類為實現演算法的具體類,其它的都是抽象類;也可以根據需要實現自己的對稱演算法。
.NET非對稱加密解密支援
RSA是當今最常用的非對稱演算法,其被應用於很多領域。RSA演算法的理論依據來自於一個大素數所具有的特性:對於給定的兩個大素數A與B,很容易計算出它們的乘積;但是,僅知道AB的成績卻很難計算原來的A與B各自的值。
.NET Frameork提供了兩個類供我們使用RSA演算法:用於加密資料的RSACryptoServiceProvider類以及用於對資料做數字簽名的DSACryptoServiceProvider類(DSA: Digital Signature Algorithm,數字簽名演算法),其類的層次結構如下
System.Security.Cryptography.AsymmetricAlgorithm
System.Security.Cryptography.DSA
System.Security.Cryptography.DSACryptoServiceProvider
System.Security.Cryptography.RSA
System.Security.Cryptography.RSACryptoServiceProvider
System.Security.Cryptography.ECDsaCng(橢圓曲線數字簽名演算法 (ECDSA) 的下一代加密技術 (CNG) 實現)
.NET雜湊演算法支援
雜湊演算法是把長長的一列資料變成較短的程式碼,這是最流行的64位雜湊金鑰。兩個最流行的雜湊演算法是SHA(Secured Hash Algorithm)和MD5(MessageDigest version5)。這些雜湊金鑰用於標記數字文件。
雜湊值是根據一個數據集合計算出來的數字。如果資料集合不同,那麼計算出來的數字基本上也是不同的。.NET Framework在其System.Security.Cryptography名稱空間下提供了一些主要的雜湊演算法,包括:SHAx、RIPEMD160、與MD5。
System.Security.Cryptography.HashAlgorithm
System.Security.Cryptography.MD5Cng(128 位雜湊演算法的 CNG(下一代加密技術)實現)
KeyedHash
鍵控雜湊演算法是依賴於金鑰的單向雜湊函式,用作訊息驗證程式碼。只有知道金鑰的人才能驗證雜湊值。加密雜湊演算法提供沒有機密的真實性。
SHA
SHA(安全雜湊演算法)是一個塊密碼,在64位的資料塊上執行,這個演算法的改進版本採用了更大的金鑰值,當然,金鑰值越大所需的計算時間越長。而且,對於相對較小的檔案,雜湊值越小就越安全,就是說,雜湊演算法的塊大小應小於或等於資料塊本身的大小。 SHA1演算法的雜湊範圍是160位。
.NET Framework也提供了更大的金鑰值演算法,分別為SHA256、SHA384和SHA512。名稱最後的數字表示其塊大小。
ASP.NET安全性中的成員資格提供程式使用SHA1加密使用者密碼。
MD5
MD5表示MessageDigest版本5。它是一個加密的單向雜湊演算法。MD5有抗偽造,計算成本低且執行簡單等特點。現在MD5已成為雜湊演算法的事實標準。
.NET Framework提供了MD5CryptoServiceProvider這個類實現MD5演算法。該類與SHA1共享同一個基類,前面的示例也只需要做簡單的改動即可成為使用MD5進行處理的例子。
RIPEMD-160
基於MD5的RIPEMD-160最早出現於歐洲,是一個使用160位的雜湊演算法。.NET也引入了對這種演算法的支援。
對稱加密解密示例
現在假設我們以TripleDES作為演算法,那麼加密的流程如下:
1. 先建立一個TripleDESCryptoServiceProvider的例項,例項名比如叫provider。
2.在provider上指定金鑰和IV,也就是它的Key屬性和IV屬性。這裡簡單解釋一下IV(initialization vector),如果一個字串(或者資料)加密之前很多部分是重複的比如ABCABCABC,那麼加密之後儘管字串是亂碼,但相關部分也是重複的。為了解決這個問題,就引入了IV,當使用它以後,加密之後即使是重複的也被打亂了。對於特定演算法,金鑰和IV的值可以隨意指定,但長度是固定,通常金鑰為128位或196位,IV為64位。金鑰和IV都是byte[]型別,因此,如果使用Encoding類來將字串轉換為byte[],那麼編碼方式就很重要,因為UTF8是變長編碼,所以對於中文和英文,需要特別注意byte[]的長度問題。
3.如果是加密,在provider上呼叫CreateEncryptor()方法,建立一個ICryptoTransform型別的加密器物件;如果是解密,在provider上呼叫CreateDecryptor()方法,同樣是建立一個ICryptoTransform型別的解密器物件。ICryptoTransform定義了加密轉換的運算,.NET將在底層呼叫這個介面。
4.因為流和byte[]是資料型別無關的一種資料結構,可以儲存和傳輸任何形式的資料,區別只是byte[]是一個靜態的概念而流是一個動態的概念。因此,.NET採用了流的方式進行加密和解密,我們可以想到有兩個流,一個是明文流,含有加密前的資料;一個是密文流,含有加密後的資料。那麼就必然有一箇中介者,將明文流轉換為密文流;或者將密文流轉換為明文流。.NET中執行這個操作的中介者也是一個流型別,叫做CryptoStream。它的建構函式如下,共有三個引數:
publicCryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
5. 當加密時,stream為密文流(注意此時密文流還沒有包含資料,僅僅是一個空流);ICryptoTransform是第3步建立的加密器,包含著加密的演算法;CryptoStreamMode列舉為Write,意思是將流經CryptoStream的明文流寫入到密文流中。最後,從密文流中獲得加密後的資料。
6. 當解密時,stream為密文流(此時密文流含有資料);ICryptoTransform是第3步建立的解密器,包含著解密的演算法;CryptoStreamMode列舉為Read,意思是將密文流中的資料讀出到byte[]陣列中,進而再由byte[]轉換為明文流、明文字串。
可見,CryptoStream總是接受密文流,並且根據CryptoStreamMode列舉的值來決定是將明文流寫入到密文流(加密),還是將密文流讀入到明文流中(解密)。下面是我編寫的一個加密解密的Helper類:
// 對稱加密幫助類
public class CryptoHelper
{
private SymmetricAlgorithm provider;
private ICryptoTransform encryptor;
private ICryptoTransform decryptor;
private const int BufferSize = 1024;
public CryptoHelper(string algorithmName)
{
provider = SymmetricAlgorithm.Create(algorithmName);
string strKey = ConfigurationManager.AppSettings["CryptoKey"];
string strIV = ConfigurationManager.AppSettings["CryptoIV"];
if (string.IsNullOrEmpty(strKey) || string.IsNullOrEmpty(strIV))
throw new ArgumentNullException("CryptoKey");
//Key
byte[] bsKey = Encoding.UTF8.GetBytes(strKey);
int keySize = provider.KeySize/8;
if (bsKey.Length != keySize)
{
byte[] key = new byte[keySize];
if (bsKey.Length > keySize)
Array.Copy(bsKey, key, keySize);
else
Array.Copy(bsKey, key, bsKey.Length);
provider.Key = key;
}
else
{
provider.Key = bsKey;
}
//IV
byte[] bsIV = Encoding.UTF8.GetBytes(strIV);
int ivSize = provider.BlockSize / 8;
if (bsIV.Length != ivSize)
{
byte[] iv = new byte[ivSize];
if(bsIV.Length > ivSize)
Array.Copy(bsIV, iv, ivSize);
else
Array.Copy(bsIV, iv, bsIV.Length);
provider.IV = iv;
}
else
{
provider.IV = bsIV;
}
encryptor = provider.CreateEncryptor();
decryptor = provider.CreateDecryptor();
}
public CryptoHelper() : this("TripleDES") { }
// Encrypt
public string Encrypt(string clearText)
{
byte[] clearBuffer = Encoding.UTF8.GetBytes(clearText);
MemoryStream clearStream = null;
MemoryStream encryptedStream = null;
CryptoStream cryptoStream = null;
try
{
clearStream = new MemoryStream(clearBuffer);
encryptedStream = new MemoryStream();
cryptoStream = new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write);
byte[] buffer = new byte[BufferSize];
int bytesRead = 0;
while((bytesRead = clearStream.Read(buffer, 0, BufferSize)) > 0)
{
cryptoStream.Write(buffer, 0, bytesRead);
}
cryptoStream.FlushFinalBlock();
buffer = encryptedStream.ToArray();
string encryptedText = Convert.ToBase64String(buffer);
return encryptedText;
}
finally
{
if (cryptoStream != null)
{
cryptoStream.Close();
cryptoStream = null;
}
if (encryptedStream != null)
{
encryptedStream.Close();
encryptedStream = null;
}
if (clearStream != null)
{
clearStream.Close();
clearStream = null;
}
if (provider != null)
{
provider.Clear();
}
}
}
// Decrypt
public string Decrypt(string encryptedText)
{
byte[] encryptedBuffer = Convert.FromBase64String(encryptedText);
byte[] buffer = new byte[BufferSize];
Stream encryptedStream = null;
MemoryStream clearStream = null;
CryptoStream cryptoStream = null;
try
{
encryptedStream = new MemoryStream(encryptedBuffer);
clearStream = new MemoryStream();
cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read);
int bytesRead = 0;
while ((bytesRead = cryptoStream.Read(buffer, 0, BufferSize)) > 0)
{
clearStream.Write(buffer, 0, bytesRead);
}
buffer = clearStream.GetBuffer();
string clearText = Encoding.UTF8.GetString(buffer, 0, (int)clearStream.Length);
return clearText;
}
finally
{
if (encryptedStream != null)
{
encryptedStream.Close();
encryptedStream = null;
}
if (clearStream != null)
{
clearStream.Close();
clearStream = null;
}
if (cryptoStream != null)
{
cryptoStream.Close();
cryptoStream = null;
}
if (provider != null)
{
provider.Clear();
}
}
}
}
非對稱加密解密示例
如果對檔案的安全性要求不是很嚴格,只是控制檔案在傳輸中的完整性,可以用雜湊演算法算出一個檔案的雜湊,然後用非對稱演算法加密並把加密後的資訊附加於檔案中,接收方使用收到的檔案中的雜湊驗證檔案的完整性,同時也能確認檔案的傳送者,即實現了簡單的檔案簽名的功能。
下面示例演示瞭如何使用RSACryptoServiceProvider類加密一個字串。其中,ExpertParameter(bool)方法根據引數的真假允許獲得公鑰/私鑰對(true)或只有公鑰(false)。
using System;
using System.Text;
using System.Security.Cryptography;
class Program
{
static void Main()
{
string sMsg = "The message to encrypt!";
string sEnc, sDec;
Encoding utf = new UTF8Encoding();
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters publicKey = rsa.ExportParameters(false);
RSAParameters publicAndPrivateKey = rsa.ExportParameters(true);
{
RSACryptoServiceProvider rsaEncryptor = new
RSACryptoServiceProvider();
rsaEncryptor.ImportParameters(publicKey);
byte[] bMsg = utf.GetBytes(sMsg);
byte[] bEnc = rsaEncryptor.Encrypt(bMsg, false);
sEnc = Convert.ToBase64String(bEnc);
}
{
RSACryptoServiceProvider rsaDecryptor = new
RSACryptoServiceProvider();
rsaDecryptor.ImportParameters(publicAndPrivateKey);
byte[] bEnc = Convert.FromBase64String(sEnc);
byte[] bDec = rsaDecryptor.Decrypt(bEnc, false);
sDec = utf.GetString(bDec);
}
Console.WriteLine("Message : " + sMsg);
Console.WriteLine("Encrypted: " + sEnc);
Console.WriteLine("Decrypted: " + sDec);
}
}
雜湊計算示例
以下以MD5演算法計算雜湊值為例,給出簡單示例。
public string MD5Crypto(string input)
{
MD5 md5Hasher = null;
try
{
// 建立? MD5CryptoServiceProvider 物件U的I例項a
md5Hasher = MD5.Create();
//計算Z哈u希o代a碼
byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
StringBuilder sBuilder = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("X2"));
}
return sBuilder.ToString();
}
finally
{
if (md5Hasher != null)
{
md5Hasher.Clear();
md5Hasher.Dispose();
md5Hasher = null;
}
}
}