1. 程式人生 > >加密解密概述及.NET中對加密解密的支援(二)

加密解密概述及.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;
                }
            }
        }