天天看點

.Net中的加密解密.Net中的加密解密

.Net中的加密解密

引言

在一些比較重要的應用場景中,通過網絡傳遞資料需要進行加密以保證安全。本文将簡單地介紹了加密解密的一些概念,以及相關的數字簽名、證書,最後介紹了如何在.NET中對資料進行對稱加密和解密。

加密和解密

說到加密,可能大家最熟悉的就是MD5了,記得幾年前我剛開始接觸Web程式設計的時候,研究的一個ASP論壇程式,它的使用者密碼就是采用的MD5進行加密。MD5實際上隻是一種散列運算,或者可以稱為單向的加密,即是說無法根據密文(加密後的資料),推導出明文(原資料)。而我們下面要說明的,是在加密後可以進行解密、還原資料的。對于欲進行加密的對象,有的人稱為消息,有的人稱為資料,有的人稱為資訊,為了避免混淆,在本文後面部分,我統一将其稱為

消息

。那麼加密是什麼呢?加密是通過對消息進行編碼,建立一種安全的交流方式,使得隻有你和你所期望的接收者能夠了解。

那麼怎麼樣才能叫安全呢?消息在接收方和發送方進行安全傳遞,一般要滿足下面三個要點:

  1. 消息的發送方能夠确定消息隻有預期的接收方可以解密(不保證第三方無法獲得,但保證第三方無法解密)。
  2. 消息的接收方可以确定消息是由誰發送的(消息的接收方可以确定消息的發送方)。
  3. 消息的接收方可以确定消息在途中沒有被篡改過(必須确認消息的完整性)。

加密通常分為兩種方式:對稱加密和非對稱加密,接下來我們先看看對稱加密。

對稱加密

對稱加密的思路非常簡單,就是含有一個稱為

密鑰

的東西,在消息發送前使用密鑰對消息進行加密,在對方收到消息之後,使用

相同的

密鑰進行解密。根據密鑰來産生加密後的消息(密文)的這一加工過程,由

加密算法

來完成

加密算法通常是公開的。它的流程如下:

  1. 發送方使用密鑰對消息進行加密。
  2. 接收方使用同樣的密鑰對消息進行解密。

可以使用下面一副圖來表示:

.Net中的加密解密.Net中的加密解密

對稱加密存在這樣兩個問題:

  1. 雖然可以通過密鑰來保證消息安全地進行傳遞,但是如何確定密鑰安全地進行傳遞?因為發送者和接收者總有一次初始的通信,用來傳遞密鑰,此時的安全如何保證?
  2. 接收者雖然可以根據密鑰來解密消息,但因為存在上面的問題,消息有可能是由第三方(非法獲得密鑰)發來的,而接收方無法辨識。

為了解決上面兩個問題,就需要介紹一下非對稱加密。

非對稱加密

非對稱加密的

接收者和發送者

都持有兩個密鑰,一個是對外公開的,稱為

公鑰

,一個是自行保管的,稱為

私鑰

。非對稱加密的規則是

由某人A的公鑰加密的消息,隻能由A的私鑰進行解密;由A的私鑰加密的消息隻能由A的公鑰解密。

此時我們可以得出接收方、發送方有兩個公鑰兩個私鑰一共四個密鑰,我們先看看兩種簡單的方式,這兩種方式都是隻使用兩個密鑰。

第一種模式隻使用接收方的公鑰和私鑰,稱為加密模式。

加密模式

在加密模式中,由消息的

接收方

釋出公鑰,持有私鑰。比如發送方要發送消息“hello,jimmy”到接收方,它的步驟是:

  1. 發送方使用接收者的公鑰進行加密消息,然後發送。
  2. 接收方使用自己的私鑰對消息進行解密。

可以使用下面一幅圖來描述:

.Net中的加密解密.Net中的加密解密

在這種模式下,如果第三方截獲了發送者發出的消息,因為他沒有接收者的私鑰,是以這個消息對他來說毫無意義。可見,它能夠滿足本文最開始提出的消息安全傳遞的要點一:

消息的發送方能夠确定消息隻有預期的接收方可以解密(不保證第三方無法獲得,但保證第三方無法解密)

除此以外,因為接收方的公鑰是公開的,任何人都可以使用這個公鑰來加密消息并發往接收者,而接收者無法對消息進行判别,無法知道是由誰發送來的。是以,它不滿足我們開始提出的消息安全傳遞的要點二:

這個問題可以在下面的認證模式中得到解決。

認證模式

在認證模式中,由消息的

發送方

釋出公鑰,持有私鑰。比如發送者要發送消息“Welcome to Tracefact.net”到接收者,它的步驟是:

  1. 發送者使用自己的私鑰對消息進行加密,然後發送。
  2. 接收者使用發送者的公鑰對消息進行解密。

可以用下面一副圖來表述:

.Net中的加密解密.Net中的加密解密

在這種模式下,假如發送方叫做Ken,接收方叫做Matthew,因為Matthew隻能使用Ken的公鑰對消息進行解密,而無法使用Molly、Sandy或者任何其他人公鑰對消息進行解密,是以他一定能夠确定消息是由Ken發送來的。是以,這個模式滿足了前面提出的消息安全傳遞的要點二。

與此同時,因為Ken的公鑰是公開的,任何截獲了該消息的第三方都能夠使用Ken的公鑰來對消息進行解密,換言之,消息現在是

不安全的

。是以,與加密模式正好相反,它無法滿足前面提出的消息安全傳遞的要點一。

而不管是采用加密模式還是認證模式,都沒有解決加密解密中的要點三:接收方必須能夠确認消息沒有被改動過。為了解決這個問題,又引入了數字簽名。

數字簽名

基本實作

數字簽名實際上就是上面非對稱加密時的認證模式,隻不過做了一點點的改進,加入了雜湊演算法。大家比較熟悉的雜湊演算法可能就是MD5了,很多開源論壇都采用了這個算法。雜湊演算法有三個特點:一是不可逆的,由結果無法推算出原資料;二是原資料哪怕是一丁點兒的變化,都會使散列值産生巨大的變化;三是不論多麼大或者多麼少的資料,總會産生固定長度的散列值(常見的為32位64位)。産生的散列值通常稱為消息的

摘要

(digest)。

那麼如何通過引入散列函數來保證資料的完整性呢?也就是接收方能夠确認消息确實是由發送方發來的,而沒有在中途被修改過。具體的過程如下:

  1. 發送方将想要進行傳遞的消息進行一個散列運算,得到消息摘要。
  2. 發送方使用自己的私鑰對摘要進行加密,将消息和加密後的摘要發送給接收方。
  3. 接收方使用發送方的公鑰對消息和消息摘要進行解密(确認了發送方)。
  4. 接收方對收到的消息進行散列運算,得到一個消息摘要。
  5. 接收方将上一步獲得的消息摘要與發送方發來的消息摘要進行對比。如果相同,說明消息沒有被改動過;如果不同,說明消息已經被篡改。

這個過程可以用下面的一副圖來表述:

.Net中的加密解密.Net中的加密解密

我們可以看出,數字簽名通過引入雜湊演算法,将非對稱加密的認證模式又加強了一步,確定了消息的完整性。除此以外,注意到上面的非對稱加密算法,

隻是對消息摘要進行了加密,而沒有對消息本身進行加密

。非對稱加密是一個非常耗時的操作,由于隻對消息摘要加密,使得運算量大幅減少,是以這樣能夠顯著地提高程式的執行速度。同時,它依然沒有確定消息不被第三方截獲到,不僅如此,因為此時消息是以明文進行傳遞,第三方甚至不需要發送方的公鑰,就可以直接檢視消息。

為了解決這樣的問題,隻需要将非對稱加密的認證模式、加密模式以及消息摘要進行一個結合就可以了,這也就是下面的進階模式。

進階實作

由于這個過程比上面稍微複雜了一些,我們将其分為發送方和接收方兩部分來看。先看看

需要執行的步驟:

  1. 将消息進行散列運算,得到消息摘要。
  2. 使用自己的私鑰對消息摘要加密(認證模式:確定了接收方能夠确認自己)。
  3. 使用接收方的公鑰對消息進行加密(加密模式:確定了消息隻能由期望的接收方解密)。
  4. 發送消息和消息摘要。

接下來我們看一下接收方所執行的步驟:

  1. 使用發送方的公鑰對消息摘要進行解密(确認了消息是由誰發送的)。
  2. 使用自己的私鑰對消息進行解密(安全地獲得了實際應獲得的資訊)。
  3. 将消息進行散列運算,獲得消息摘要。
  4. 将上一步獲得的消息摘要 和 第一步解密的消息摘要進行對比(确認了消息是否被篡改)。

可以看到,通過上面這種方式,使用了接收方、發送方全部的四個密鑰,再配合使用消息摘要,使得前面提出的安全傳遞的所有三個條件全都滿足了。那麼是不是這種方法就是最好的呢?不是的,因為我們已經說過了,非對稱加密是一種很耗時的操作,是以這個方案是很低效的。實際上,我們可以通過它來解決對稱加密中的密鑰傳遞問題,如果你已經忘記了可以翻到前面再看一看,也就是說,我們可以使用這裡的進階實作方式來進行對稱加密中密鑰的傳遞,對于之後實際的資料傳遞,采用對稱加密方式來完成,因為此時已經是安全的了。

證書機制

與數字簽名相關的一個概念就是證書機制了,證書是用來做什麼呢?在上面的各種模式中,我們一直使用了這樣一個假設,就是接收方或者發送方所持有的、對方的公鑰總是正确的(确實是對方公布的)。而實際上除非對方手把手将公鑰交給我們,否則如果不采取措施,雙方在網絡中傳遞公鑰時,一樣有可能被篡改。那麼怎樣解決這個問題呢?這時就需要證書機制了:可以引入一個

公正的

第三方,當某一方想要釋出公鑰時,它将自身的身份資訊及公鑰送出給這個第三方,第三方對其身份進行證明,如果沒有問題,則将其資訊和公鑰打包成為

證書(Certificate)

。而這個公正的第三方,就是常說的

證書頒發機構(Certificate Authority)

。當我們需要擷取公鑰時,隻需要獲得其證書,然後從中提取出公鑰就可以了。

.NET中加密解密的支援

對稱加密和解密

相信通過前面幾頁的叙述,大家已經明白了加密解密、數字簽名的基本原理,下面我們看一下在.NET中是如何來支援加密解密的。正如上面我們所進行的分類,.NET中也提供了兩組類用于加密解密,一組為對稱加密,一組為非對稱加密,如下圖所示:

.Net中的加密解密.Net中的加密解密

上面的類按照名稱還可以分為兩組,一組字尾為“CryptoServiceProvider”的,是對于底層Windows API的包裝類,一組字尾為“Managed”,是在.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。它的構造函數如下,共有三個參數:

public CryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)

當加密時,stream為密文流(注意此時密文流還沒有包含資料,僅僅是一個空流);ICryptoTransform是第3步建立的加密器,包含着加密的算法;CryptoStreamMode枚舉為Write,意思是将流經CryptoStream的明文流寫入到密文流中。最後,從密文流中獲得加密後的資料。

當解密時,stream為密文流(此時密文流含有資料);ICryptoTransform是第3步建立的解密器,包含着解密的算法;CryptoStreamMode枚舉為Read,意思是将密文流中的資料讀出到byte[]數組中,進而再由byte[]轉換為明文流、明文字元串。

可見,CryptoStream總是接受密文流,并且根據CryptoStreamMode枚舉的值來決定是将明文流寫入到密文流(加密),還是将密文流讀入到明文流中(解密)。下面是我編寫的一個加密解密的Helper類:

// 對稱加密幫助類

public class CryptoHelper {

    // 對稱加密算法提供器

    private ICryptoTransform encryptor;     // 加密器對象

    private ICryptoTransform decryptor;     // 解密器對象

    private const int BufferSize = 1024;

    public CryptoHelper(string algorithmName, string key) {

        SymmetricAlgorithm provider = SymmetricAlgorithm.Create(algorithmName);

        provider.Key = Encoding.UTF8.GetBytes(key);

        provider.IV = new byte[] { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };

        encryptor = provider.CreateEncryptor();

        decryptor = provider.CreateDecryptor();

    }

    public CryptoHelper(string key) : this("TripleDES", key) { }

    // 加密算法

    public string Encrypt(string clearText) {

        // 建立明文流

        byte[] clearBuffer = Encoding.UTF8.GetBytes(clearText);

        MemoryStream clearStream = new MemoryStream(clearBuffer);

        // 建立空的密文流

        MemoryStream encryptedStream = new MemoryStream();

        CryptoStream cryptoStream =

            new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write);

        // 将明文流寫入到buffer中

        // 将buffer中的資料寫入到cryptoStream中

        int bytesRead = 0;

        byte[] buffer = new byte[BufferSize];

        do {

            bytesRead = clearStream.Read(buffer, 0, BufferSize);

            cryptoStream.Write(buffer, 0, bytesRead);

        } while (bytesRead > 0);

        cryptoStream.FlushFinalBlock();

        // 擷取加密後的文本

        buffer = encryptedStream.ToArray();

        string encryptedText = Convert.ToBase64String(buffer);

        return encryptedText;

    // 解密算法

    public string Decrypt(string encryptedText) {

        byte[] encryptedBuffer = Convert.FromBase64String(encryptedText);

        Stream encryptedStream = new MemoryStream(encryptedBuffer);

        MemoryStream clearStream = new MemoryStream();

            new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read);

            bytesRead = cryptoStream.Read(buffer, 0, BufferSize);

            clearStream.Write(buffer, 0, bytesRead);

        buffer = clearStream.GetBuffer();

        string clearText =

            Encoding.UTF8.GetString(buffer, 0, (int)clearStream.Length);

        return clearText;

    public static string Encrypt(string clearText, string key) {

        CryptoHelper helper = new CryptoHelper(key);

        return helper.Encrypt(clearText);

    public static string Decrypt(string encryptedText, string key) {

        return helper.Decrypt(encryptedText);

}

我們可以對上面這個類進行一個簡單的測試:

static void Main(string[] args) {

    string key = "ABCDEFGHIJKLMNOP";

    string clearText = "歡迎通路www.tracefact.net";

    CryptoHelper helper = new CryptoHelper(key);

    string encryptedText = helper.Encrypt(clearText);

    Console.WriteLine(encryptedText);

    clearText = CryptoHelper.Decrypt(encryptedText, key);

    Console.WriteLine(clearText);

應該可以看到下面的輸出結果:

.Net中的加密解密.Net中的加密解密

總結

首先向大家表示歉意,我并沒有寫.NET中非對稱加密的部分,因為我很少用到,是以這部分我并不是很熟悉,但是原理現在應該已經很清楚了,我想等到需要的時候再去學習如何來使用它們。到那個時候,我也會對這篇文章再次進行更新。通過這篇文章,相信大家對于加密、解密、數字簽名等這些安全方面的概念已經有了一個初步的認識,同時也學習到了如何在.NET下進行對稱加密。

感謝閱讀,希望這篇文章能給你帶來幫助!

繼續閱讀