HTTPS之加密方式

ricolee發表於2019-03-29

何謂安全?

對於資訊保安性的重要性,我想大家都不會否認。那麼具體來說應該具有哪些特性才能稱之為安全呢?舉個簡單的例子:我給你傳送一條訊息“借給我100元”,當你收到這條訊息並且處理後你的賬戶裡面會少出來100塊,我的賬戶會多出來100塊。在這個過程中,你是訊息接收方,我是訊息傳送方。

1.作為通訊雙方的你我都不希望讓其他人能讀懂這條訊息,這是資訊的機密性,即訊息在傳遞過程中不被其他人解讀。

2.作為通訊雙方的你我都不希望訊息內容變成"借老子1000塊!"(操,借錢還這麼牛逼,100塊都不給你,還要1000塊!死去...),這是資訊的完整性,即可以校驗出資訊在傳送過程中是否被篡改。

3.作為訊息接收方的你需要確認是不是真正的我給你發的借錢的訊息吧,會不會是個詐騙犯要騙我100塊!這是對資訊的認證,即接收者要可以驗證訊息的傳送者確實是自己希望的傳送者。

4.作為訊息接收方的你肯定不希望在借給了我100塊之後,我耍無賴失口否認說沒借過你錢,這是資訊的不可否認性,即訊息傳送者不可以否認說這個資訊不是我傳送的。

總結來說,在通訊過程中,滿足這4個特徵:機密性,完整性,認證,不可否認性,就可以認為資訊是安全的。那麼接下來的幾個小節來介紹一下有那些工具可以使得我們在傳遞訊息的時候具有以上4個特徵。

對稱密碼-對稱金鑰

英文:Symmetric Cryptography

對稱密碼加密可以保障資訊的機密性。舉一個簡單的例子,一把鎖,兩把相同的鑰匙,就是對稱密碼;即:使用相同的金鑰來加密和解密。沒有金鑰的其他人是無法解讀資訊的真正內容是什麼的。常見的兩個對稱加密標準有DESAES

DES是一種對稱金鑰加密演算法,在1976年被美國聯邦政府的國家標準局確定為聯邦資料處理標準,隨後在國際上廣泛流傳開來。它基於使用56位金鑰的對稱演算法。現在現在已經不是一種安全的加密方法,主要因為它使用的56位金鑰過短。後來又發展出了3DES(即執行三次DES加密)。由於DES已經不再安全,後來又推出了新的對稱加密標準AES,採用的演算法為Rijndael。演算法的具體實現邏輯這裡不去解釋,這裡關注的是如何利用它們(即,保障資訊的機密性的手段)。看一下簡單的加密解密的數學公式:

  1. 加密:encrypted_data = encrypt_function(message, key)
  2. 解密:message = decrypt_function(encrypted_data, key)

C#使用AES的程式碼如下:

/// <summary>
///  AES加密
/// </summary>
/// <param name="key">128bit,192bit,125bit</param>
/// <returns></returns>
public static byte[] AESEncrypt(this byte[] value, byte[] key)
{
    //todo 引數檢查
    using (var symmetricAlgorithm = Aes.Create())
    {
        symmetricAlgorithm.Key = key;
        symmetricAlgorithm.Mode = CipherMode.ECB;
        symmetricAlgorithm.Padding = PaddingMode.PKCS7;
        //加密
        using (var encryptor = symmetricAlgorithm.CreateEncryptor())
        {
            return encryptor.TransformFinalBlock(value, 0, value.Length);
        }
    }
}

/// <summary>
///  AES解密
/// </summary>
/// <param name="key">128bit,192bit,125bit</param>
/// <returns></returns>
public static byte[] AESDecrypt(this byte[] value, byte[] key)
{
    //todo 引數檢查
    using (var symmetricAlgorithm = Aes.Create())
    {
        symmetricAlgorithm.Key = key;
        symmetricAlgorithm.Mode = CipherMode.ECB;
        symmetricAlgorithm.Padding = PaddingMode.PKCS7;
        //解密
        using (var encryptor = symmetricAlgorithm.CreateDecryptor())
        {
            return encryptor.TransformFinalBlock(value, 0, value.Length);
        }
    }
}

static void Main()
{
    var value = "lnh".ToBytes(Encoding.UTF8);
    //構造128bit的key,guid正好是128,權且當作key了。
    var key = Guid.NewGuid().ToByteArray();
    var encryptedData = value.AESEncrypt(key);
    var decryptedData = encryptedData.AESDecrypt(key);
    var decryptedDataString = Encoding.UTF8.GetString(decryptedData);
    Console.WriteLine();
}
複製程式碼

遺留問題

金鑰配送問題:共享的金鑰如何交到接受訊息方的手上呢?雙方可以事先共同約定一個金鑰,但是這種辦法是無法滿足網際網路規模的需要的,網際網路規模的環境是假設通訊雙方事先並不知道對方的存在,怎麼事先約定呢,行不通吧。

下面接下來的公鑰金鑰可以解決這個問題。

公鑰密碼-非對稱金鑰

英文:Asymmetric Cryptography

對稱密碼加密可以解決資訊的機密性的問題,但是卻無法提供雙方如何才能得到加密所用金鑰的途徑。我們回到最初的目的想一想,我們想要的機密性的核心在於別人無法取得資訊的真實內容,也就是解密;而如何生成這個機密的資訊,其實並不是我們關注的點,你能生成,他能生成,都沒區別,只要我控制住只有我才能解密,那麼機密性的問題就解決了。所以解決金鑰配送的問題的關鍵就在於,把金鑰分成兩部分,一個加密用,一個解密用,它們總是成對出現的。配送的是加密用的金鑰(也叫公鑰),解密用的叫私鑰,這個只有我自己知道,不會在任何地方傳輸,那麼也就不存在配送的問題了。

其實很多計算機中的問題都是無解的,往往卻又是有解決辦法的,它的解決辦法其實並不是直接的解決這個問題,而是規避掉這個問題,使得它不在是一個問題的。比如金鑰配送的問題,如果說我們有安全的方式解決金鑰配送的問題,直接使用這個安全的方式配送我們想要傳遞的資訊不就是了,我們還繞個彎配送金鑰幹什麼呢。公鑰密碼其實並未解決金鑰配送的問題,而是使得它不再是個問題,即:公鑰可以公開給任何人,不再需要保密(本質上來說,金鑰和待加密的資訊同樣重要),而是通過控制解密來達到我們想要的機密性,繞過了如何機密的配送金鑰的問題。

公鑰密碼就是這麼一個簡單的原理:公鑰(=public key)加密私鑰(=private key)解密,它可以保障資訊的機密性,同時解決金鑰的配送問題。那麼這個時候通訊雙方的流程就是這樣的:

  1. 傳送方向接收方請求一個public key
  2. 傳送方使用 public key 加密訊息, public key 和加密的訊息洩露都沒關係,因為只有 private key 才能解密;
  3. 接收方用 private key 解密訊息。

至於如何產生出來這樣一對 public keyprivate key 以及相對於的加密解密演算法,這其中涉及到很複雜的數學問題,這裡就不展開介紹了(筆者也不懂...)。我們看一下最廣泛使用的公鑰密碼演算法RSA在C#裡面怎麼使用吧:

/// <summary>
///  RSA加密
/// </summary>
/// <param name="publicKey">公鑰</param>
/// <returns></returns>
public static byte[] RSAEncrypt(this byte[] value, string publicKey)
{
    //todo 引數檢查
    using (var asymmetricAlgorithm = new RSACryptoServiceProvider())
    {
        asymmetricAlgorithm.FromXmlString(publicKey);
        return asymmetricAlgorithm.Encrypt(value,false);
    }
}

/// <summary>
///  RSA解密
/// </summary>
/// <param name="privateKey">私鑰</param>
/// <returns></returns>
public static byte[] RSADecrypt(this byte[] value, string privateKey)
{
    //todo 引數檢查
    using (var asymmetricAlgorithm = new RSACryptoServiceProvider())
    {
        asymmetricAlgorithm.FromXmlString(privateKey);
        return asymmetricAlgorithm.Decrypt(value,false);
    }
}

static void Main()
{
    string privateKey;
    string publicKey;
    using (var asymmetricAlgorithm = RSA.Create())
    {
        privateKey = asymmetricAlgorithm.ToXmlString(true);
        publicKey = asymmetricAlgorithm.ToXmlString(false);
    }

    var value = "lnh".ToBytes(Encoding.UTF8);
    //公鑰加密
    var encryptedData = value.RSAEncrypt(publicKey);
    //私鑰解密
    var decryptedData = encryptedData.RSADecrypt(privateKey);

    var decryptedDataString = Encoding.UTF8.GetString(decryptedData);
    Console.WriteLine();
}
複製程式碼

.Net庫中已經提供了公鑰密碼相關的類,開箱即用:

image

對公鑰金鑰的攻擊

中間人攻擊:這鐘型別的攻擊發生在上述流程中的第一步,即傳送方A向接收方B請求 public key 的時候。這時有一個攔路打劫的傢伙M,截獲了這個 public key ,自己據為己有。然後M把自己的一個 public key 給到了A,A是渾然不覺,傻乎乎的用這個假的 public key 加密了資訊,傳送了出去,這時候M攔截到了這個訊息,用自己的 private key 解密了這個訊息,然後篡改一番,用真正的 public key 進行加密,發給了B。這個時候B以為是A傳送的,A也以為自己發給了B,其實都被M給玩了...文字可能不是很清晰,看圖:

image

遺留問題

公鑰的認證問題:公鑰金鑰可以解決規避掉的配送問題,但是新問題又來了,**這個公鑰真的是你的嗎?**針對上述的中間人攻擊,其實我們發現,獲取公鑰的這一方並不能確認自己收到的公鑰就是自己真正請求的那一方提供的。這個問題先放一放(後續會介紹),下面先看看保障資訊的完整性方面有那些工具可用。

密碼雜湊函式

英文:Cryptographic hash function

密碼雜湊函式可以保障的資訊完整性,用來校驗要傳遞的資訊是否被篡改過。比如通常在下載檔案的時候,官方的網站上都會列出來其MD5或者SHA1的值來校驗。它的工作原理和要求大致如下:

輸入一組資料message,然後得到一組簡短的資料hash,只要是採用相同的演算法,輸入message就能得到hash:hash = hash_function (message); 其過程是不可逆的,你不能由hash得出message; 滿足message的微小變化會(比如只改動1位元組)會使得hash產生巨大的變化(就好比兩個雙胞胎,各處都很像,但是他們的指紋卻不是相同的); 兩組不同的訊息message1message2,不能得出相同的hash(理論上可以,只是要儘可能的使這個過程困難)。 常用的密碼雜湊函式(演算法)有Message Digest Algorithm以及Secure Hash Algorithm

MD5

英文:Message Digest Algorithm 5

中文明為訊息摘要演算法第五版,這也說明其實它也有前面幾個版本,比如MD4(這裡就不介紹了)。MD5演算法是輸入任意長度的資料(Message),然後算出固定長度的資料 16byte=128bits ,用16進製表示這16個byte就是32位。C#使用MD5的程式碼如下:

 /// <summary>
 /// MD5摘要演算法
 /// </summary>
 /// <param name="value"></param>
 /// <returns>128 bits,16 byte array</returns>
 public static byte[] ToMD5(this byte[] value)
 {
     if (value == null || value.Length == 0)
     {
         throw new ArgumentNullException(nameof(value));
     }
     using (var hashAlgorithm = MD5.Create())
     {
         return hashAlgorithm.ComputeHash(value);
     }
 }
複製程式碼

再一次指出,md5的結果是固定的16byte=128bits ,用16進製表示是32個字元。網上由很多的16進位制16個字元的md5,其實這都不是完整的md5,只是擷取了32位中的16位而已。

SHA

英文:Secure Hash Algorithm

從使用者的角度來看,MD5SHA沒有什麼本質區別,差異在於其演算法的實現方式,生成的hash的長度,其抗攻擊破解的難度不一樣。此外由於SHA的強度比MD5要大,所以在計算SHA的時候,所消耗的資源(時間,空間都有)也會比MD5要多。即使如此,現在MD5(128bit)和SHA-1(160bit)均已遭到了破解:news.cnblogs.com/n/563589/。S…SHA256的程式碼如下:

/// <summary>
/// SHA256雜湊演算法
/// </summary>
/// <returns>256 bits,32 byte array</returns>
public static byte[] ToSHA256(this byte[] value)
{
    if (value == null || value.Length == 0)
    {
        throw new ArgumentNullException(nameof(value));
    }
    using (var hashAlgorithm = SHA256.Create())
    {
        return hashAlgorithm.ComputeHash(value);
    }
}
複製程式碼

NET的庫已經幫我們封裝好了密碼雜湊函式相關的類,開箱即用。

image

密碼雜湊函式的實際應用

  1. 檢查檔案是否被修改:上面一開始舉得例子下載檔案的例子。
  2. 基於口令的加密:通常我們在儲存使用者的密碼的時候,都會採用這種方式(除非你是csdn),一般還會輔助的加上鹽。
  3. 訊息認證碼:後面介紹到到。
  4. 數字簽名:後面會介紹到。
  5. 偽隨機數生成器:後面會介紹到。

針對密碼雜湊函式的攻擊

  • 強碰撞性攻擊:比如上面提到的Google破解了SHA-1,即使用大量的計算來找出兩個資料message不一樣,但是hash值卻一樣的過程,如果找到了這樣的兩塊資料,那麼再使用這個hash作為資料的完整性校驗的手段,其實已經沒有意義了。解決辦法是升級SHA-2,增大計算出這樣兩塊資料的難度。

  • 暴力破解:比如網上很多的MD5的解密,其實原理在於他們有大量的MD5 hash庫,比如 123456 的MD5是 e10adc3949ba59abbe56e057f20f883e ,那麼在你給我一個 e10adc3949ba59abbe56e057f20f883e 的時候,我就知道你的原文是 123456 。解決辦法是加鹽,增大這種暴力比對的難度。

針對上面兩種攻擊方式都是在於增加破解難度,使其在現有的計算能力下不能輕易的被攻破,沒有絕對的安全,只是相對上來說是安全的,當破解你帶來的收益要低於其破解成本的時候,你才是安全的

遺留問題

hash被篡改了:比如上面下載檔案的時候官方會給出MD5或者SHA1的hash值,這裡我們假設一下,官方提供hash值的渠道被黑掉了,給了你一個篡改過的hash值,然後你下載了一個被篡改過的檔案,你是分辨不出來的。其實我們下載檔案,然後比對官方給的hash值,這裡是假設官方的hash值是沒有被篡改的。

那麼接下來的訊息認證碼MAC是可以解決這個問題。

訊息認證碼

英文:Message Authentication Code

訊息認證碼(MAC)的作用就是在保障完整性的基礎上,同時提供認證(認證=訊息是來自真正的傳送者)的功能,用來解決上述密碼雜湊函式遺留的問題。可以簡單的這樣理解,MAC是在密碼雜湊函式+共享金鑰後算出的hash值,由於金鑰是隻有通訊雙方才知道的,那麼就可以認為通過MAC得到的hash可以保障資訊的完整性以及同時提供認證的能力。這裡我們假設雙方不存在金鑰配送的問題(即雙方已經持有相同的金鑰,至於是通過什麼方式傳遞的,這裡先不關心)。

使用密碼雜湊函式可以實現MAC,這種方式稱為HMAC(Hash Message Authentication Code):tools.ietf.org/html/rfc210…en.wikipedia.org/wiki/Hash-b… = mac_function (message,key)。C#中使用HMAC的程式碼如下:

/// <summary>
/// HMACSHA1演算法
/// </summary>
/// <returns>160 bits,20 byte array</returns>
public static byte[] ToHMACSHA1(this byte[] value,byte[] key)
{
    if (value == null || value.Length == 0)
    {
        throw new ArgumentNullException(nameof(value));
    }
    if (key == null || key.Length == 0)
    {
        throw new ArgumentNullException(nameof(key));
    }
    using (var macAlgorithm =new HMACSHA1())
    {
        macAlgorithm.Key = key;
        return macAlgorithm.ComputeHash(value);
    }
}

static void Main()
{
    var value = "lnh".ToBytes();
    var key = "123".ToBytes();
    var mac = value.ToHMACSHA1(key);
    Console.WriteLine();
}
複製程式碼

.Net類庫中開箱即用的MAC相關的類,開箱即用:

image

訊息認證碼的實際應用

  • SWIFT:此SWIFT非蘋果的Swift語言,而是Society for Worldwide Interbank Financial(環球銀行金融電信協會)的簡寫。在銀行之間進行傳遞交易訊息時,會用到MAC來確認訊息的完整性以及對訊息進行認證。在沒有使用公鑰密碼進行金鑰交換之前,訊息認證碼使用的共享金鑰時靠人力通過11路來完成的。
  • IPsec:增強版Ip協議,用來認證和校驗訊息的完整性。
  • SSL/TLS:後續會介紹到。

針對訊息認證碼的攻擊

  • 重放攻擊:比如你給我轉賬100元,攜帶了mac的訊息,其實我並不用破解你的訊息和mac,原封不動拿你的訊息重複給我轉賬就是了,你信不信我可以把你賬戶裡面所有的錢都變成我的...解決辦法是對訊息新增編號和時間戳,使得訊息接收方針對這個訊息只處理一次。
  • 金鑰推測攻擊:和密碼雜湊碼的暴力攻擊是類似的,不再細說。

遺留問題

訊息不是我傳送的,是你自己偽造的:基於MAC的原理是在於通訊雙方共享金鑰,那麼訊息接收方可以判斷訊息是來自真正的傳送者,但是卻無法向第三者證明這一點,為什麼呢?因為訊息的接收方也有金鑰啊,訊息傳送者完全可以說這是訊息接收者自己用這個共享金鑰生成的訊息,畢竟金鑰雙方都有。

那麼接下來的數字簽名是可以解決這個問題。

數字簽名(Digital Signature)

上面的MAC可以保障資訊的完整性,同時具有提供訊息認證的能力,但是又遺留了一個可以否認訊息是我傳送的問題。究其原因在於通訊雙方使用了同一個金鑰來生成MAC,你說是他生成的,他說是你生成的。那麼怎麼解決呢,其實也簡單,雙方使用不同的金鑰;訊息傳送方使用簽名金鑰生成一個“簽名”(就像簽字畫押按手印一樣的道理,表示我承認這些資訊是我傳送的),訊息接收方使用另外驗證金鑰來驗證這個簽名,這其實就是數字簽名。

數字簽名對簽名金鑰驗證金鑰進行了區分,驗證金鑰無法生成簽名;此外簽名金鑰只能由簽名人持有,而驗證金鑰則可以由任何想要驗證簽名的人持有。回想一下,這個簽名金鑰和驗證金鑰是不是感覺似曾相識,對了,和上面我們提到的公鑰密碼中的公鑰和私鑰非常類似吧

公鑰密碼:金鑰分為加密金鑰和解密金鑰,用加密金鑰無法進行解密;解密金鑰只有需要解密的人持有,而加密金鑰則是任何需要加密的人都可以持有。

實際上,數字簽名和公鑰金鑰有著非常緊密的聯絡,簡單點來說,數字簽名是通過把公鑰密碼“反過來用”來實現的:

私鑰 / 簽名金鑰 公鑰 / 驗證金鑰
公鑰密碼 接收者解密時使用 傳送者加密時使用
數字簽名 簽名者生成簽名時使用 驗證者驗證簽名時使用
誰持有金鑰 個人持有 只要需要,任何人都可以持有

數字簽名的實現是:簽名人用私鑰加密一段資訊來生成簽名,驗證者使用公鑰來解密這個簽名,如果可以解密成功,則說明驗證成功。覺得很奇怪是不是?為什麼能用公鑰解密就證明簽名驗證通過了呢?其實這是由於私鑰和金鑰是成對出現的(具有嚴密的數學關係),只有公鑰才能解密與之配對的私鑰加密的資訊,那麼既然能夠解密,那麼這個訊息肯定是持有私鑰的這一方生成的。你估計還會想到一個問題,公鑰是公開的呀,你有我由他也有,那麼私鑰生成的這個加密的簽名大家都可以解密,根本沒有機密性啊。是的,這樣理解是完全正確的,私鑰加密的資訊是不具備機密性的;這是因為數字簽名是用來提供訊息的不可否認性的,它並不關心機密性的問題。

上面我們說到“簽名人用私鑰加密{一段資訊}來生成簽名”。那麼問題來了,這{一段資訊}是什麼資訊?關於這一段資訊我們由兩種選擇:1是訊息本身,2是訊息的hash。

下圖是對訊息本身進行簽名的過程:

image

實際中我們一般採用的是對訊息的hash進行簽名的方式,因為訊息本身可能非常大,加密解密過程會非常消耗資源。再C#中使用RSA來實現數字簽名:

/// <summary>
/// 數字簽名
/// </summary>
/// <returns></returns>
public static byte[] DigitalSignature(this byte[] value, string privateKey)
{
    using (var asymmetricAlgorithm = new RSACryptoServiceProvider())
    {
        asymmetricAlgorithm.FromXmlString(privateKey);
        return asymmetricAlgorithm.SignData(value, SHA1.Create());
    }
}

/// <summary>
/// 數字簽名驗證
/// </summary>
/// <returns></returns>
public static bool DigitalSignatureVerify(this byte[] value, string publicKey,byte[] digitalSignature)
{
    using (var asymmetricAlgorithm = new RSACryptoServiceProvider())
    {
        asymmetricAlgorithm.FromXmlString(publicKey);
        return asymmetricAlgorithm.VerifyData(value, SHA1.Create(), digitalSignature);
    }
}

static void Main()
{
    string privateKey;
    string publicKey;
    using (var asymmetricAlgorithm = RSA.Create())
    {
        privateKey = asymmetricAlgorithm.ToXmlString(true);
        publicKey = asymmetricAlgorithm.ToXmlString(false);
    }

    var value = "lnh".ToBytes(Encoding.UTF8);

    //用私鑰生成數字簽名
    var digitalSignature = value.DigitalSignature(privateKey);

    //用公鑰驗證數字簽名
    var verified = value.DigitalSignatureVerify(publicKey, digitalSignature);

    Console.WriteLine();
}
複製程式碼

數字簽名本身的實現是使用了公鑰金鑰相關的演算法。

數字簽名的實際應用

  • 公鑰證書:上面在介紹公鑰密碼的時候,遺留的一個公鑰認證的問題,即我們怎麼才能知道自己拿到的公鑰是不是真正的公鑰,而不是被第三方偽造的呢?可以把公鑰當作訊息,然後施加數字簽名,所得到的就是公鑰證書,關於證書的知識後續部落格會介紹。
  • SSL/TLS:SSL/TLS在認證伺服器是否合法的時候會使用伺服器證書,就是上面提到的公鑰證書;於此相對,伺服器在對客戶端進行認證的時候,也會使用客戶端證書。關於SSL/TLS後續部落格會介紹。

針對數字簽名的攻擊

  • 中間人攻擊:在公鑰密碼這一小節中提到了中間人攻擊,因為數字簽名其實就是使用它來實現的,那麼對於數字簽名來說,中間人攻擊也是具有相同的威脅。
  • 對密碼雜湊函式的攻擊:數字簽名使用了密碼雜湊函式,那麼數字簽名也面臨同樣的威脅。
  • 利用數字簽名攻擊公鑰金鑰:這塊好複雜,筆者研究明白再補充( ╯□╰ )。。。

遺留問題

數字簽名可以識別出篡改和偽裝,還可以防止否認,也就是說數字簽名可以提供資訊保安中的完整性、認證和不可否認性這3點的保障(很強大有木有)。然而這一切都基於一個假設“公鑰必須是真正的傳送者提供的”,和公鑰金鑰陷入了同一個問題。我們發現自己陷入了一個死迴圈:數字簽名可以用來識別篡改、偽裝以及否認的,但是為此我們又需要從一個沒有被偽裝的真正的傳送者那裡得到一個沒有被篡改的金鑰......這是一個雞生蛋蛋生雞的問題。

06

細心的讀者或許可以看出來,上面我們的加密、雜湊、mac,簽名也好,消費的資料都是byte[],而byte[]是不方便書寫、列印、複製和貼上的,下面看一看byte[]編碼的問題。換換腦子,雞生蛋還是蛋生雞的問題放一放先。

偽隨機數生成器

英文:Pseudo-Random Number Generator

隨機數大家不陌生,但是隨機數怎麼就和資訊保安扯上關係了呢?其實想一想我們在給自己的賬號設定密碼的時候,是不是都會盡量的讓其他人不會輕易的猜到我們的密碼,雖然並不是隨機,但是它就像是滿足了隨機數的一個特徵:不可預測性。那麼對於資訊保安來說來說,也是用到了這個特定,當然還有隨機數的隨機性,不可重複性這兩點特徵。

隨機性:完全雜亂的序列,沒有統計學偏差; 不可預測性:不能由已經得到的隨機數才猜測出下一個隨機數是什麼; 不可重複性:不能生成重複的隨機數。 根據生成的隨機數是否滿足這3點要求(1<2<3,依次增強)。大致可以劃分偽弱偽隨機數,強偽隨機數,真隨機數(強度依次增大)。

隨機性 不可預測性 不可重複性
弱偽隨機數 × × 只具備隨機性
強偽隨機數 × 同時具備不可預測性
真隨機數 同時具備不可重複性

僅僅依靠軟體我們是無法生成真隨機數的,這裡我們只關注以下偽隨機數(即強偽隨機數的生成,可用於密碼學安全)的生成,比如一個典型的生成器如下:

11

生成器自己維護一個內部狀態,同時接受一個隨機數的種子,來生成具體的隨機數。具體是實現方式有利用密碼雜湊函式(單向性支撐了不可預測性)、利用加密金鑰作為隨機數的種子的一部分(金鑰的機密性支援了不可預測性)等等。

在C#可以使用的偽隨機數生成方式:

//1. Random
 var random = new Random();
 var random1 = random.Next(100);
 Console.WriteLine(random1);
 
 //2. Guid 
 var random2 = Guid.NewGuid().ToString("N");
 Console.WriteLine(random2);
 
 //3. RNGCryptoServiceProvider
 RandomNumberGenerator randomNumberGenerator = new RNGCryptoServiceProvider();
 var random3Bytes = new byte[32];
 randomNumberGenerator.GetBytes(random3Bytes);
 var random3 = random3Bytes.ToHexString();
 Console.WriteLine(random3);
複製程式碼

一般情況下,Guid即可滿足要求(但是隻有固定的16byte),如需更高強度的偽隨機數,可以使用 RNGCryptoServiceProvider 來生成任意長度的隨機數。

偽隨機數的實際應用

  • 生成金鑰:對稱密碼和訊息認證碼;
  • 生成金鑰對:公鑰金鑰和數字簽名;
  • 生成nonce:防止重放攻擊;
  • 生成salt:鹽用於增強基於口令的密碼的加密。

針對偽隨機數生成器的攻擊

偽隨機數的程式結構可以說很簡單,但是其中的每個環節都有可能成為被攻擊的突破口。

  • 對種子的攻擊:如果暴露了種子,那麼其實攻擊者就可以得到其所有的偽隨機數(假設攻擊者知道其內部演算法的情況下)。
  • 對偽隨機數池的攻擊:如果我們實現生成了一大堆的偽隨機數,用的時候從裡面取一個,那麼這個儲存這些預先生成的偽隨機數的地方,就可能會被洩露。

混合密碼系統

針對密碼相關的基本工具介紹就暫時可以告一段落了,回顧總結以下有這6個**(對稱密碼、公鑰金鑰、密碼雜湊函式、訊息認證碼、數字簽名、偽隨機數生成器)**基本工具,下面我們用這6個基本工具來組合一些高階的工具出來。

上一篇中介紹到了對稱密碼(比如AES)和公鑰密碼(比如RSA),公鑰密碼解決了對稱密碼的金鑰配送問題(其實是繞過了)。

那麼如果作為接收方我想要回復傳送方的訊息怎麼辦?按照公鑰密碼的機制,我是不能用自己的私鑰加密資訊發出去的,因為擁有持有我的公鑰的任何人都是可以解密這個資訊的。所以,如果僅使用公鑰密碼,那麼就需要通訊雙方都持有對方的公鑰+自己的私鑰。這個成本是很高昂的,首先公鑰加密解密的速度是會比對稱密碼加密低2~3個數量級,也就是幾百倍的差異;其次雙方都面臨著針對公鑰的認證問題(防止中間人攻擊)。

那麼我們可以結合之前提到的一些工具,組合一下,來得到一個價效比高的加密通訊方式,即使用以下三個基本工具,組合一個高階點的工具(同時具備對稱密碼和公鑰密碼的優點):

  1. 對稱密碼:因為其加密解密的速度比較快,所消耗的資源相對來說比較小,所以用來在通訊中承擔真正的加密和解密任務。
  2. 公鑰密碼:用來傳遞對稱密碼所需的金鑰。
  3. 偽隨機數生成器:用來生成對稱密碼的金鑰。

看一下混合密碼的加密過程:

12

上圖是用偽隨機數生成器生成一個加密用的會話金鑰,來加密明文;同時,把這個會話金鑰作為公鑰密碼中的明文,用公鑰加密;然後把這兩個密文組合在一起,同時傳送給接收方。這裡的公鑰金鑰起到的是一個保證會話金鑰機密性的作用,並未直接用來加密真正的明文(又想起來一句話:電腦科學的中任何問題,都可以通過新增一箇中間層來解決,此言不虛;在另外一個[認證授權]系列的部落格中,筆者也有這樣的體會)。來看以下接收方解密的流程:

13

相對於加密過程來說,是完全反過來的一個過程,就不再解釋了。看一段C#中實際使用的程式碼:

static void Main()
{
    string privateKey;
    string publicKey;
    using (var asymmetricAlgorithm = RSA.Create())
    {
        privateKey = asymmetricAlgorithm.ToXmlString(true);
        publicKey = asymmetricAlgorithm.ToXmlString(false);
    }
    // 傳送者加密
    var hybridCiphertext = HybridEncrypt(publicKey, "lnh-明文");
    Console.WriteLine(hybridCiphertext);

    // 接收者解密
    var plaintext = HybridDecrypt(privateKey, hybridCiphertext);
    Console.WriteLine(plaintext);

    Console.ReadKey();
}

static string HybridEncrypt(string publicKey, string plaintext)
{
    var plaintextBytes = plaintext.ToBytes(Encoding.UTF8);

    //1. 生成偽隨機數,作為會話簽名
    var sessionKey = SecurityHelper.BuildPseudoRandomNumber(16);

    //2. 使用sessionKey作為AES的金鑰進行加密
    var ciphertextBytes = plaintextBytes.AESEncrypt(sessionKey);

    //3. 使用公鑰對會話金鑰進行加密
    var sessionkeyCiphertextBytes = sessionKey.RSAEncrypt(publicKey);

    //4. 模擬合成的訊息
    var hybridCiphertext = ciphertextBytes.ToHex() + "." + sessionkeyCiphertextBytes.ToHex();

    return hybridCiphertext;
}

static string HybridDecrypt(string privateKey, string hybridCiphertext)
{
    //1. 分離合成的密文
    var ciphertext = hybridCiphertext.Split('.')[0];
    var sessionkeyCiphertext = hybridCiphertext.Split('.')[1];

    var ciphertextBytes = ciphertext.HexToBytes();
    var sessionkeyCiphertextBytes = sessionkeyCiphertext.HexToBytes();

    //2. 用私鑰解密得到會話金鑰
    var sessionkey = sessionkeyCiphertextBytes.RSADecrypt(privateKey);

    //3. 用會話密碼解密
    var plaintextBytes = ciphertextBytes.AESDecrypt(sessionkey);

    return plaintextBytes.GetString(Encoding.UTF8);
}
複製程式碼

混合密碼系統的實際應用

  • SSL/TLS:最常見的一個應用場景了,後續會介紹。

遺留問題

混合密碼系統只能說是降低了單純的公鑰密碼帶來的成本問題,而公鑰密碼遺留的公鑰認證問題,在混合密碼系統中依然存在。同時使用了偽隨機數生成器,混合密碼系統也會面臨針對偽隨機生成器的一些攻擊。

證書(Certificate)- 為公鑰新增數字簽名

數字簽名遺留下問題,彙總在一起的核心就是驗證公鑰必須是真正的傳送者提供的

所以,想要解決這個問題單靠純粹的技術手段是行不通了,我們陷入了一個死迴圈,因此引入了一個社會學中的信任體系來轉移我們所面臨的問題,即證書以及相關體系結構,提供逐級的信任保障。我們先看看證書是一個什麼東西,以及證書的這套相關體系如何提供這種“信任”保障的。

我們從一出生就會和各種各樣的證書打交道,比如出生證,學生證,身份證,駕照,學位證等等,它們都有一個共同點,就是有你本人的真實資訊以及開具證明的機構的蓋章。那麼在需要提供證明你就是你的地方,出具這個證書即可,如果對方不信任你的證書,則可以到開具證書的機構來校驗。假如你提供一個假證,而對方沒有嚴格的審查的話,或許你是可以矇混過關的。

計算機領域的證書和現實社會中的各種證書的工作原理是完全一樣的,因為其工作在計算機體系中,也被稱為“數字證書”。計算機中數字證書是這樣定義的:由證書授權中心進行數字簽名的,包含公鑰以及其擁有者資訊的一個檔案。注意:證書的真正用途在於為公鑰提供認證功能,所以有時候也叫做公鑰證書。我們使用這個被稱做證書的檔案來轉移我們在資訊保安層面所面臨的死迴圈的問題,為什麼說是轉移而不是解決呢,這是因為你拿到一個證書後,也需要進行校驗吧,而校驗又需要一個真正的傳送者提供的公鑰才行,那麼你就需要另外一個證書來保障,然後你就會一直的迴圈下去,,,這也是為什麼在計算機體系中有根證書的存在,以及相關的證書授權認證中心會是一個層級的關係,這就是為了在你不信任一個證書的時候,可以繼續往上一個層級來尋求驗證,直到根證書。那麼問題就來了,假如你也不相信根證書怎麼辦?這其實是一個無法回答的問題,筆者想起來之前讀《人類簡史》的時候,有一個至今烙印在腦海中的觀點:“如今的社會,是一個由想象所構建的秩序”。其實想一想,也確實是如此。比如你為何相信國家的存在呢,為何會把錢存進銀行呢。你拿出來一張毛爺爺(從物理的角度來看,它就是一張紙而已)為什麼就能從飯店買來一堆食物,這其實就是你相信它,對方也相信它,所有人都相信它,背後有銀行體系為其擔保,那麼什麼為銀行提供擔保呢,背後有我們的國家提供保障,這就是一個信任的體系。計算機體系的數字證書也是基於這麼一個共同的想象所構建的信任秩序。補充一個新聞:Google 宣佈將完全取消對沃通和 StartCom 所有證書的信任(news.cnblogs.com/n/573409/),…

PKI

英文全稱:Public Key Infrastructure

證書得以執行的這個基本的體系稱為PKI(Public Key Infrastructure),即公鑰基礎設施,它是一套以公鑰密碼為核心的技術規範標準的一個代名詞。總體來說,PKI由3個要素組成:PKI的消費者;認證機構(Certificate Authority,簡稱CA);證書倉庫。我們常說的CA證書,就是由CA機構簽名頒發的證書。CA負責生成金鑰對(也可由使用者提供)、負責對使用者身份進行認證、負責生成並頒發證書、負責作廢證書(en.wikipedia.org/wiki/Certif… CA的證書是自簽名的(也就是自己證明自己),其下級機構逐層簽名,構成一個金字塔似的結構。當然你平時自己也可以生成自簽名的證書,但是除了你自己,其他地方是不認可你這個證書的(就好比你拿一張白紙,寫上這是100塊,然後別人就相信你它值100塊嗎?),想要得以正常執行,是需要使用者主動確認表示認可你這個證書才行。比如我們用Fiddler抓取HTTPS的內容的時候,其實Fiddler自己生成了一個自簽名的根證書,然後你主動的確認信賴它,只有這樣,證書構造的這個驗證鏈才能得以正常執行。想起來12306就自己搞了一個自簽名的證書,想必大家都有印象吧,,,需要自己下載下來證書,然後匯入到計算機中,再確認信任它;其實這也是一個很尷尬的事情,全球最大的幾家CA清一色不是美國就是俄羅斯。

公鑰證書包含的資訊

好了,分析完數字證書這套體系為什麼能夠運轉起來為我們提供公鑰的認證的保障之後。看看計算機中的公鑰證書是什麼樣的,我們拿https://www.google.com的做例子(F12,開啟安全選項卡即可),證書的相關資訊如下:

證書除了包含公鑰、簽名演算法和證書的層級結構(比如google這個的頂級頒發機構是GeoTurst Global CA),還有一些證書的序號,版本資訊,有效時間等等。這些資訊由一個證書資料格式的標準規範來規定的,一個很通用的格式是x509,感興趣的可以瞭解一下,這裡就不介紹了。C#中有很多X509相關的類可以供我們使用。比如來讀取一下上述google的這個證書資訊(我把證書匯出為了base64格式的資料,可以方便的直接包含在程式碼中):

private static readonly string GoogleBase64FormatPublicKeyCertificate = @"
-----BEGIN CERTIFICATE-----
MIIEgDCCA2igAwIBAgIIaCtCibL6TxQwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTcwNjIxMTQzNTUwWhcNMTcwOTEzMTM1MzAw
WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEXMBUGA1UEAwwOd3d3
Lmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFHtwc
1ECzMNuXPshUwS9IkyKPuHYA6WXa3ohXW/wMHo0IKnu5WgXmduLS6cGoFlT3oq3P
PXJz11gKpdBJeoLs/g4lG3mOnGRSQbjtsWsXCPsunMjeq0vTfidJ2Gt+1eMHh5B4
qcgOxbXEK9AE6GZGCL3MSV2lE2oG0GDpStZkLhKt11GE+qrLSQCpH9XgzknHdrvz
OU6Kl3e5W+4QO6rTq5285D18Ep6Cugf39JbZQZHSu0ejLnmtSOHwUg1i/vbJrDN/
yVwEySn+drxv0CzPDrTMiqGLVxBOSwN9wU9cRphiLLSdE4Sy2p77jCNLWzbcQQ5P
5f+2hLXb2Z/N1kAZAgMBAAGjggFLMIIBRzAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
KwYBBQUHAwIwGQYDVR0RBBIwEIIOd3d3Lmdvb2dsZS5jb20waAYIKwYBBQUHAQEE
XDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3J0
MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29vZ2xlLmNvbS9vY3NwMB0G
A1UdDgQWBBTxnCXke+dTXQNZyBd5gWpjj8kdKjAMBgNVHRMBAf8EAjAAMB8GA1Ud
IwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMCEGA1UdIAQaMBgwDAYKKwYBBAHW
eQIFATAIBgZngQwBAgIwMAYDVR0fBCkwJzAloCOgIYYfaHR0cDovL3BraS5nb29n
bGUuY29tL0dJQUcyLmNybDANBgkqhkiG9w0BAQsFAAOCAQEAgHtFBvVyIQwRSrUC
RbPu0fZFl9HJCJ0FBXVlQl0JO5PdRTtXlkfDqeoZcE3l562/FotKyaPKRyhktYDb
5tnYo74q1gKxfmTjXEtkBeUDUAlNzepuXYudu43A5athR/GPIDxXQvQc4Lakmafi
LJFTZLw7ZjmkU0mkt3uaiUXTuOiA+5hjjGLzFzRpRXvUcqIggGUTVJ4v7HSmOl3x
tjePNc8ps3bivp8WtB4jR6k+PvVmlYDN/Uf7+cwmOEtrXUBCrVwA/LL+j3mkwHK0
49h5xyjmB/ndmH/HgjY2DSzu2HMekkPJEnPWmkxqRP2c08UqQoUbXE9zdL35Ys5A
JRO+1w==
-----END CERTIFICATE-----
";
static void Main()
{
    var bytes = GoogleBase64FormatPublicKeyCertificate.ToBytes(Encoding.ASCII);
    var x509 = new X509Certificate2(bytes);
    var text = x509.ToString(true);
    Console.WriteLine(text);
    Console.ReadKey();
}
複製程式碼

PKCS

英文全稱:Public Key Cryptography Standards

證書的相關格式以及交換標準在PKCS(Public-Key Cryptography Standards)中有詳細的定義。常見的證書編碼格式:

  • PEM :Privacy Enhanced Mail,以"-----BEGIN..."開頭,以 "-----END..."結尾,中間內容是base64編碼的文字。
  • DER :Distinguished Encoding Rules,二進位制格式。

常見到的幾個證書副檔名:

  • .p7b :特點是其包含相關其證書鏈,不含私鑰。
  • .cer / .crt :一般是採用DER編碼的二進位制格式,不含私鑰。
  • .pem :一般是採用PEM編碼的base64格式,不含私鑰,另外其檔案一般採用ASCII編碼。
  • .pfx / .p12 :一般是採用DER編碼的二進位制格式,包含公鑰和私鑰的。

參考 & 引用

密碼工具箱-blackheart

書籍

相關文章