[信息安全] 2.密碼工具箱(續)
在上一篇1.密碼工具箱中介紹了一些密碼技術相關的一些基本工具,同時遺留了一個雞生蛋蛋生雞的問題和公鑰的認證問題( ̄▽ ̄)",這里再補充幾個常用的工具先。
1. 偽隨機數生成器(Pseudo-Random Number Generator)
隨機數大家不陌生,但是隨機數怎么就和信息安全扯上關系了呢?其實想一想我們在給自己的賬號設置密碼的時候,是不是都會盡量的讓其他人不會輕易的猜到我們的密碼,雖然并不是隨機,但是它就像是滿足了隨機數的一個特征:不可預測性。那么對于信息安全來說來說,也是用到了這個特定,當然還有隨機數的隨機性,不可重復性這兩點特征。
- 隨機性:完全雜亂的序列,沒有統計學偏差;
- 不可預測性:不能由已經得到的隨機數才猜測出下一個隨機數是什么;
- 不可重復性:不能生成重復的隨機數。
根據生成的隨機數是否滿足這3點要求(1<2<3,依次增強)。大致可以劃分偽弱偽隨機數,強偽隨機數,真隨機數(強度依次增大)。
| 隨機性 | 不可預測性 | 不可重復性 | ||
| 弱偽隨機數 | √ | × | × | 只具備隨機性 |
| 強偽隨機數 | √ | √ | × | 同時具備不可預測性 |
| 真隨機數 | √ | √ | √ | 同時具備不可重復性 |
僅僅依靠軟件我們是無法生成真隨機數的,這里我們只關注以下偽隨機數(即強偽隨機數的生成,可用于密碼學安全)的生成,比如一個典型的生成器如下:

生成器自己維護一個內部狀態,同時接受一個隨機數的種子,來生成具體的隨機數。具體是實現方式有利用密碼散列函數(單向性支撐了不可預測性)、利用加密密鑰作為隨機數的種子的一部分(密鑰的機密性支持了不可預測性)等等。
在C#可以使用的偽隨機數生成方式:
1 //1. Random 2 var random = new Random(); 3 var random1 = random.Next(100); 4 Console.WriteLine(random1); 5 6 //2. Guid 7 var random2 = Guid.NewGuid().ToString("N"); 8 Console.WriteLine(random2); 9 10 //3. RNGCryptoServiceProvider 11 RandomNumberGenerator randomNumberGenerator = new RNGCryptoServiceProvider(); 12 var random3Bytes = new byte[32]; 13 randomNumberGenerator.GetBytes(random3Bytes); 14 var random3 = random3Bytes.ToHexString(); 15 Console.WriteLine(random3);
一般情況下,Guid即可滿足要求(但是只有固定的16byte),如需更高強度的偽隨機數,可以使用 RNGCryptoServiceProvider 來生成任意長度的隨機數。
1.2 偽隨機數的實際應用
1.3 針對偽隨機數生成器的攻擊
偽隨機數的程序結構可以說很簡單,但是其中的每個環節都有可能成為被攻擊的突破口。
- 對種子的攻擊:如果暴露了種子,那么其實攻擊者就可以得到其所有的偽隨機數(假設攻擊者知道其內部算法的情況下)。
- 對偽隨機數池的攻擊:如果我們實現生成了一大堆的偽隨機數,用的時候從里面取一個,那么這個存儲這些預先生成的偽隨機數的地方,就可能會被泄露。
2. 混合密碼系統
針對密碼相關的基本工具介紹就暫時可以告一段落了,回顧總結以下有這6個(對稱密碼、公鑰密鑰、密碼散列函數、消息認證碼、數字簽名、偽隨機數生成器)基本工具,下面我們用這6個基本工具來組合一些高級的工具出來。
上一篇中介紹到了對稱密碼(比如AES)和公鑰密碼(比如RSA),公鑰密碼解決了對稱密碼的密鑰配送問題(其實是繞過了)。
復習以下公鑰密碼的核心流程:發送消息這一方先從消息接收方這里請求一個公鑰,然后用公鑰加密需要發送的信息,接收方使用自己獨自持有的私鑰來解密信息。
那么如果作為接收方我想要回復發送方的消息怎么辦?按照公鑰密碼的機制,我是不能用自己的私鑰加密信息發出去的,因為擁有持有我的公鑰的任何人都是可以解密這個信息的。所以,如果僅使用公鑰密碼,那么就需要通信雙方都持有對方的公鑰+自己的私鑰。這個成本是很高昂的,首先公鑰加密解密的速度是會比對稱密碼加密低2~3個數量級,也就是幾百倍的差異;其次雙方都面臨著針對公鑰的認證問題(防止中間人攻擊)。
那么我們可以結合之前提到的一些工具,組合一下,來得到一個性價比高的加密通信方式,即使用以下三個基本工具,組合一個高級點的工具(同時具備對稱密碼和公鑰密碼的優點):
看一下混合密碼的加密過程:

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

相對于加密過程來說,是完全反過來的一個過程,就不再解釋了。看一段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); }
2.1 混合密碼系統的實際應用
SSL/TLS:最常見的一個應用場景了,后續會介紹。
2.2 遺留問題
混合密碼系統只能說是降低了單純的公鑰密碼帶來的成本問題,而公鑰密碼遺留的公鑰認證問題,在混合密碼系統中依然存在。同時使用了偽隨機數生成器,混合密碼系統也會面臨針對偽隨機生成器的一些攻擊。
3. 證書(Certificate)- 為公鑰添加數字簽名
總結一下上篇的數字簽名遺留下問題和上一小節遺留的問題,匯總在一起的核心就是驗證公鑰必須是真正的發送者提供的。
數字簽名遺留的問題:數字簽名可以識別出篡改和偽裝,還可以防止否認,也就是說數字簽名可以提供信息安全中的完整性、認證和不可否認性這3點的保障(很強大有木有)。然而這一切都基于一個假設“公鑰必須是真正的發送者提供的”,和公鑰密鑰陷入了同一個問題。我們發現自己陷入了一個死循環:數字簽名可以用來識別篡改、偽裝以及否認的,但是為此我們又需要從一個沒有被偽裝的真正的發送者那里得到一個沒有被篡改的密鑰......這是一個雞生蛋蛋生雞的問題。
所以,想要解決這個問題單靠純粹的技術手段是行不通了,我們陷入了一個死循環,因此引入了一個社會學中的信任體系來轉移我們所面臨的問題,即證書以及相關體系結構,提供逐級的信任保障。我們先看看證書是一個什么東西,以及證書的這套相關體系如何提供這種“信任”保障的。
我們從一出生就會和各種各樣的證書打交道,比如出生證,學生證,身份證,駕照,學位證等等,它們都有一個共同點,就是有你本人的真實信息以及開具證明的機構的蓋章。那么在需要提供證明你就是你的地方,出具這個證書即可,如果對方不信任你的證書,則可以到開具證書的機構來校驗。假如你提供一個假證,而對方沒有嚴格的審查的話,或許你是可以蒙混過關的。
計算機領域的證書和現實社會中的各種證書的工作原理是完全一樣的,因為其工作在計算機體系中,也被稱為“數字證書”。計算機中數字證書是這樣定義的:由證書授權中心進行數字簽名的,包含公鑰以及其擁有者信息的一個文件。注意:證書的真正用途在于為公鑰提供認證功能,所以有時候也叫做公鑰證書。我們使用這個被稱做證書的文件來轉移我們在信息安全層面所面臨的死循環的問題,為什么說是轉移而不是解決呢,這是因為你拿到一個證書后,也需要進行校驗吧,而校驗又需要一個真正的發送者提供的公鑰才行,那么你就需要另外一個證書來保障,然后你就會一直的循環下去,,,這也是為什么在計算機體系中有根證書的存在,以及相關的證書授權認證中心會是一個層級的關系,這就是為了在你不信任一個證書的時候,可以繼續往上一個層級來尋求驗證,直到根證書。那么問題就來了,假如你也不相信根證書怎么辦?這其實是一個無法回答的問題,筆者想起來之前讀《人類簡史》的時候,有一個至今烙印在腦海中的觀點:“如今的社會,是一個由想象所構建的秩序”。其實想一想,也確實是如此。比如你為何相信國家的存在呢,為何會把錢存進銀行呢。你拿出來一張毛爺爺(從物理的角度來看,它就是一張紙而已)為什么就能從飯店買來一堆食物,這其實就是你相信它,對方也相信它,所有人都相信它,背后有銀行體系為其擔保,那么什么為銀行提供擔保呢,背后有我們的國家提供保障,這就是一個信任的體系。計算機體系的數字證書也是基于這么一個共同的想象所構建的信任秩序。補充一個新聞:Google 宣布將完全取消對沃通和 StartCom 所有證書的信任(https://news.cnblogs.com/n/573409/),這就是對方不再信任你的根證書的情況。
3.1 PKI(Public Key Infrastructure)
證書得以運行的這個基本的體系稱為PKI(Public Key Infrastructure),即公鑰基礎設施,它是一套以公鑰密碼為核心的技術規范標準的一個代名詞。總體來說,PKI由3個要素組成:PKI的消費者;認證機構(Certificate Authority,簡稱CA);證書倉庫。我們常說的CA證書,就是由CA機構簽名頒發的證書。CA負責生成密鑰對(也可由用戶提供)、負責對用戶身份進行認證、負責生成并頒發證書、負責作廢證書(https://en.wikipedia.org/wiki/Certificate_revocation_list)。CA是由層級結構的,正是這個層級結構構建了一套證書的驗證鏈條,其中Root CA的證書是自簽名的(也就是自己證明自己),其下級機構逐層簽名,構成一個金字塔似的結構。當然你平時自己也可以生成自簽名的證書,但是除了你自己,其他地方是不認可你這個證書的(就好比你拿一張白紙,寫上這是100塊,然后別人就相信你它值100塊嗎?),想要得以正常運行,是需要用戶主動確認表示認可你這個證書才行。比如我們用Fiddler抓取HTTPS的內容的時候,其實Fiddler自己生成了一個自簽名的根證書,然后你主動的確認信賴它,只有這樣,證書構造的這個驗證鏈才能得以正常運行。想起來12306就自己搞了一個自簽名的證書,想必大家都有印象吧,,,需要自己下載下來證書,然后導入到計算機中,再確認信任它;其實這也是一個很尷尬的事情,全球最大的幾家CA清一色不是美國就是俄羅斯。
3.2 公鑰證書包含的信息
好了,分析完數字證書這套體系為什么能夠運轉起來為我們提供公鑰的認證的保障之后。看看計算機中的公鑰證書是什么樣的,我們拿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(); }
3.3 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編碼的二進制格式,包含公鑰和私鑰的。
4. 總結
本篇完善了密碼相關的工具箱,增加了偽隨機數,混合密碼系統,以及通過轉移問題而解決公鑰的認證問題的數字證書,以及數字證書的工作機制,和其相關的一些細節點(當然都是一筆帶過了,想了解更詳細的信息還需讀者自行研究)。本系列后續的會拿這個工具箱來剖析HTTPS是如何工作的。如有錯誤指出,歡迎指正!
參考
代碼:https://github.com/linianhui/code/blob/master/src/SecurityHelper.cs
偽隨機數生成器:https://en.wikipedia.org/wiki/Random_number_generation
公鑰證書:https://en.wikipedia.org/wiki/Public_key_certificate
CRL(Certificate revocation list):https://en.wikipedia.org/wiki/Certificate_revocation_list
Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile:https://tools.ietf.org/html/rfc5280
浙公網安備 33010602011771號