本文是對HTTP—TCP/IP—SOCKET理解及淺析的補充,如有需要,請查看上篇文章。
SSL與TLS

SSL協(xié)議由Netscape公司開發(fā),歷史可以追溯到Netscape Navigator瀏覽器統(tǒng)治互聯(lián)網(wǎng)的時代。
1996年5月, TLS工作組成立,開始將SSL從Netscape遷移至IETF。由于Microsoft和Netscape當時正在為Web的統(tǒng)治權(quán)爭得不可開交,整個遷移過程進行得非常緩慢、艱難。最終, TLS 1.0于1999年1月問世,見RFC 2246。
盡管與SSL 3相比,版本修改并不大,但是為了取悅Microsoft,協(xié)議還是進行了更名??。
2006年4月,下一個版本TLS 1.1才問世,僅僅修復(fù)了一些關(guān)鍵的安全問題。然而,協(xié)議的重要更改是作為TLS擴展于2003年6月發(fā)布的,并被集成到了協(xié)議中,這比大家的預(yù)期早了好幾年。
2008年8月, TLS 1.2發(fā)布。該版本添加了對已驗證加密的支持,并且基本上刪除了協(xié)議說明中所有硬編碼的安全基元,使協(xié)議完全彈性化。
SSL和TLS都是加密協(xié)議,旨在基于不安全的基礎(chǔ)設(shè)施提供安全通信。
這意味著,如果正確部署這些協(xié)議,你就可以對互聯(lián)網(wǎng)上的任意一個服務(wù)打開通信信道,并且可以確信你會與正確的服務(wù)器通信,安全地交換信息(你的數(shù)據(jù)不會被他人截取,而且在接收時會保持原樣)。這些協(xié)議保護著通信鏈路即傳輸層,這也是TLS名稱的由來。
安全不是TLS的唯一目標。 TLS實際上有以下四個主要目標(按優(yōu)先順序排列)。
- 加密安全
這是主要問題:為任意愿意交換信息的雙方啟用安全通信。 - 互操作性
獨立的編程人員應(yīng)該能夠使用通用的加密參數(shù)開發(fā)程序和庫,使它們可以相互通信。 - 可擴展性
你很快就會看到, TLS是一種能高效開發(fā)和部署加密協(xié)議的框架。其重要目標是獨立于實
際使用的加密基元(例如密碼和散列函數(shù)),從而不需要創(chuàng)建新的協(xié)議,就允許從一個基
元遷移到另一個。 - 效率
最終的目標是在實現(xiàn)上述所有目標的基礎(chǔ)上保持性能成本在可接受的范圍內(nèi)。這需要盡
量減少昂貴的加密操作的執(zhí)行次數(shù),并提供一個會話緩存方案,以避免這些加密操作在
隨后的連接中被執(zhí)行。
互聯(lián)網(wǎng)的核心是建立在IP( internet protocol)和TCP( transmission control protocol)協(xié)議之上的,這些協(xié)議用于將數(shù)據(jù)分割成小數(shù)據(jù)包進行傳輸。IP和TCP不是唯一易受攻擊的協(xié)議,還有一系列其他路由協(xié)議用于協(xié)助發(fā)現(xiàn)網(wǎng)絡(luò)上的其他計算機。
如果部署了加密,攻擊者也許有能力得到加密數(shù)據(jù)的訪問權(quán)限,但是不能解密數(shù)據(jù)或者篡改數(shù)據(jù)。為了避免偽裝攻擊, SSL和TLS依賴另外一項被稱為公鑰基礎(chǔ)設(shè)施( public key infrastructure,PKI)的重要技術(shù),確保將流量發(fā)送到正確的接收端。
為了理解SSL和TLS的運作,我們需要從描述網(wǎng)絡(luò)通信的理論模型入手,即開放系統(tǒng)互聯(lián)( open systems interconnection, OSI)模型,參見表1-1。簡單來說,所有功能都被映射到七個層上。最底層是最接近物理通信鏈路的層,后面的層依次建立在其他層之上,提供更高級別的抽象。最頂層就是應(yīng)用層,攜帶著應(yīng)用數(shù)據(jù)。

以這種方式安排通信可以清晰地劃分概念:高層的協(xié)議不必擔心在底層實現(xiàn)的功能。進一步說,不同層次的協(xié)議可以加入通信或者從通信中刪除,一種底層協(xié)議可以服務(wù)于多種上層協(xié)議SSL和TLS是這一原則如何在實踐中運用的一個重要示例。它用于TCP協(xié)議之上,上層協(xié)議(如HTTP)之下。當不需要加密時,可以將TLS從模型中去掉,這并不會對上層協(xié)議產(chǎn)生影響(它們將直接與TCP協(xié)同工作)。當需要加密時,就可以利用TLS加密HTTP,以及其他TCP協(xié)議(比如SMTP、 IMAP等)。
TLS中的握手協(xié)議
握手是TLS協(xié)議中最精密復(fù)雜的部分。在這個過程中,通信雙方協(xié)商連接參數(shù),并且完成身份驗證。根據(jù)使用的功能的不同,整個過程通常需要交換6~10條消息。根據(jù)配置和支持的協(xié)議擴展的不同,交換過程可能有許多變種。
在使用中經(jīng)??梢杂^察到以下三種流程:
(1) 完整的握手,對服務(wù)器進行身份驗證;
(2) 恢復(fù)之前的會話采用的簡短握手;
-
(3) 對客戶端和服務(wù)器都進行身份驗證的握手。
握手協(xié)議消息的標頭信息包含消息類型( 1字節(jié))和長度( 3字節(jié)),余下的信息則取決于消息類型:
struct {
HandshakeType msg_type;
uint24 length;
HandshakeMessage message;
} Handshake;
2.2.1 完整的握手
每一個TLS連接都會以握手開始。如果客戶端此前并未與服務(wù)器建立會話,那么雙方會執(zhí)行一次完整的握手流程來協(xié)商TLS會話。握手過程中,客戶端和服務(wù)器將進行以下四個主要步驟。
- (1) 交換各自支持的功能,對需要的連接參數(shù)達成一致。
- (2) 驗證出示的證書,或使用其他方式進行身份驗證。
- (3) 對將用于保護會話的共享主密鑰達成一致。
- (4) 驗證握手消息并未被第三方團體修改。
在實際使用中,第2步和第3步都是密鑰交換(更通用的說法是密鑰生成)的一部分,
密鑰交換是一個單獨的步驟。我更喜歡將它們分開來說,用以強調(diào)協(xié)議的安全性取決
于正確的身份驗證。身份驗證有效地在TLS的外層工作。如果沒有身份驗證,主動攻
擊者就可以將自身嵌入會話,并冒充會話的另一端。
本節(jié)會討論最常見的TLS握手流程,就是一種在不需要身份驗證的客戶端與需要身份驗證的服務(wù)器之間的握手,如圖2-2所示。

(1) 客戶端開始新的握手,并將自身支持的功能提交給服務(wù)器。
(2) 服務(wù)器選擇連接參數(shù)。
(3) 服務(wù)器發(fā)送其證書鏈(僅當需要服務(wù)器身份驗證時)。
(4) 根據(jù)選擇的密鑰交換方式,服務(wù)器發(fā)送生成主密鑰的額外信息。
(5) 服務(wù)器通知自己完成了協(xié)商過程。
(6) 客戶端發(fā)送生成主密鑰所需的額外信息。
(7) 客戶端切換加密方式并通知服務(wù)器。
(8) 客戶端計算發(fā)送和接收到的握手消息的MAC并發(fā)送。
(9) 服務(wù)器切換加密方式并通知客戶端。
-
(10) 服務(wù)器計算發(fā)送和接收到的握手消息的MAC并發(fā)送。
假設(shè)沒有出現(xiàn)錯誤,到這一步,連接就建立起來了,可以開始發(fā)送應(yīng)用數(shù)據(jù)。現(xiàn)在讓我們了
解一下這些握手消息的更多細節(jié)。
- ClientHello
在一次新的握手流程中, ClientHello消息總是第一條消息。這條消息將客戶端的功能和首選項傳送給服務(wù)器。客戶端會在新建連接后,希望重新協(xié)商或者響應(yīng)服務(wù)器發(fā)起的重新協(xié)商請求(由HelloRequest消息指示)時,發(fā)送這條消息。在下面的例子中,你可以觀察到ClientHello消息。為了更簡潔,我減少了一些信息展示,但是包含了所有的關(guān)鍵元素。
Handshake protocol: ClientHello
Version: TLS 1.2
Random
Client time: May 22, 2030 02:43:46 GMT
Random bytes: b76b0e61829557eb4c611adfd2d36eb232dc1332fe29802e321ee871
Session ID: (empty)
Cipher Suites
Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
Suite: TLS_RSA_WITH_AES_128_GCM_SHA256
Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA
Suite: TLS_RSA_WITH_AES_128_CBC_SHA
Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA
Suite: TLS_RSA_WITH_RC4_128_SHA
Compression methods
Method: null
Extensions
Extension: server_name
Hostname: www.feistyduck.com
Extension: renegotiation_info
Extension: elliptic_curves
Named curve: secp256r1
Named curve: secp384r1
Extension: signature_algorithms
Algorithm: sha1/rsa
Algorithm: sha256/rsa
Algorithm: sha1/ecdsa
Algorithm: sha256/ecdsa
可以看到,絕大多數(shù)消息字段光看名稱就很容易理解,而且消息的結(jié)構(gòu)也很容易理解。
- Version
協(xié)議版本( protocol version)指示客戶端支持的最佳協(xié)議版本。 - Random
隨機數(shù)( random)字段包含32字節(jié)的 數(shù)據(jù)。當然,只有28字節(jié)是隨機生成的;剩余的4字節(jié)包含額外的信息,受客戶端時鐘的影響。準確來說,客戶端時間與協(xié)議不相關(guān),而且協(xié)議規(guī)格文檔中言及此事時也很清楚(“基本的TLS協(xié)議不需要正確設(shè)置時鐘,更高層或應(yīng)用協(xié)議可以定義額外的需求項?!保?;該字段是1994年在Netscape Navigator中發(fā)現(xiàn)了一個嚴重故障之后,為了防御弱隨機數(shù)生成器而引入的。盡管這個字段曾經(jīng)一直含有精確時間的部分,但現(xiàn)在仍然有人擔心客戶端時間可能被用于大規(guī)模瀏覽器指紋采集,所以一些瀏覽器會給它們的時間添加時鐘扭曲(正如你在示例中所看到的那樣),或者簡單地發(fā)送隨機的4字節(jié)。在握手時,客戶端和服務(wù)器都會提供隨機數(shù)。這種隨機性對每次握手都是獨一無二的,在身份驗證中起著舉足輕重的作用。它可以防止重放攻擊,并確認初始數(shù)據(jù)交換的完整性。 - Session ID
在第一次連接時,會話ID( session ID)字段是空的,這表示客戶端并不希望恢復(fù)某個已存在的會話。在后續(xù)的連接中,這個字段可以保存會話的唯一標識。服務(wù)器可以借助會話ID在自己的緩存中找到對應(yīng)的會話狀態(tài)。典型的會話ID包含32字節(jié)隨機生成的數(shù)據(jù),這些數(shù)據(jù)本身并沒有什么價值。 - Cipher Suites
密碼套件( cipher suite)塊是由客戶端支持的所有密碼套件組成的列表,該列表是按優(yōu)先級順序排列的。 - Compression
客戶端可以提交一個或多個支持壓縮的方法。默認的壓縮方法是null,代表沒有壓縮。 - Extensions
擴展( extension)塊由任意數(shù)量的擴展組成。這些擴展會攜帶額外數(shù)據(jù)。我會在本章后面對最常見的擴展進行討論。
- ServerHello
ServerHello消息的意義是將服務(wù)器選擇的連接參數(shù)傳送回客戶端。這個消息的結(jié)構(gòu)與ClientHello類似,只是每個字段只包含一個選項。
Handshake protocol: ServerHello
Version: TLS 1.2
Random
Server time: Mar 10, 2059 02:35:57 GMT
Random bytes: 8469b09b480c1978182ce1b59290487609f41132312ca22aacaf5012
Session ID: 4cae75c91cf5adf55f93c9fb5dd36d19903b1182029af3d527b7a42ef1c32c80
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
Compression method: null
Extensions
Extension: server_name
Extension: renegotiation_info
服務(wù)器無需支持客戶端支持的最佳版本。如果服務(wù)器不支持與客戶端相同的版本,可以提供某個其他版本以期待客戶端能夠接受。
- Certificate
典型的Certificate消息用于攜帶服務(wù)器X.509證書鏈。<u>證書鏈是以ASN.1 DER編碼的一系列證書,一個接著一個組合而成。</u>主證書必須第一個發(fā)送,中間證書按照正確的順序跟在主證書之后。根證書可以并且應(yīng)該省略掉,因為在這個場景中它沒有用處。服務(wù)器必須保證它發(fā)送的證書與選擇的算法套件一致。
比方說,公鑰算法與套件中使用的必須匹配。除此以外,一些密鑰交換算法依賴嵌入證書的特定數(shù)據(jù),而且要求證書必須以客戶端支持的算法簽名。所有這些都表明服務(wù)器需要配置多個證書(每個證書可能會配備不同的證書鏈,Certificate消息是可選的,因為并非所有套件都使用身份驗證,也并非所有身份驗證方法都需要證書。
更進一步說,雖然消息默認使用X.509證書,但是也可以攜帶其他形式的標志;一些套件就依賴PGP密鑰。
ServerKeyExchange
ServerKeyExchange消息的目的是攜帶密鑰交換的額外數(shù)據(jù)。消息內(nèi)容對于不同的協(xié)商算法套件都會存在差異。在某些場景中,服務(wù)器不需要發(fā)送任何內(nèi)容,這意味著在這些場景中根本不會發(fā)送ServerKeyExchange消息。ServerHelloDone
ServerHelloDone消息表明服務(wù)器已經(jīng)將所有預(yù)計的握手消息發(fā)送完畢。在此之后,服務(wù)器會等待客戶端發(fā)送消息。ClientKeyExchange
ClientKeyExchange消息攜帶客戶端為密鑰交換提供的所有信息。這個消息受協(xié)商的密碼套件的影響,內(nèi)容隨著不同的協(xié)商密碼套件而不同。ChangeCipherSpec
ChangeCipherSpec消息表明發(fā)送端已取得用以生成連接參數(shù)的足夠信息,已生成加密密鑰,并且將切換到加密模式??蛻舳撕头?wù)器在條件成熟時都會發(fā)送這個消息。
注意ChangeCipherSpec不屬于握手消息,它是另一種協(xié)議,只有一條消息,作為它的子協(xié)議進行實現(xiàn)。這個設(shè)計的結(jié)果是這條消息不是握手完整性驗證算法的一部分,這使得正確實現(xiàn)TLS更為困難。
在2014年6月,人們發(fā)現(xiàn)OpenSSL對于ChangeCipherSpec消息的處理不正確,使得OpenSSL為主動網(wǎng)絡(luò)攻擊敞開了大門。同樣的問題也出現(xiàn)在其他所有子協(xié)議中。主動網(wǎng)絡(luò)攻擊者利用緩沖機制在首次握手時發(fā)送未經(jīng)驗證的警報消息,更可以在開始加密以后破環(huán)真正的警報消息。為了避免更嚴重的問題,應(yīng)用數(shù)據(jù)協(xié)議消息必須等到首次握手完成以后才能開始發(fā)送。
- Finished
Finished消息意味著握手已經(jīng)完成。消息內(nèi)容將加密,以便雙方可以安全地交換驗證整個握手完整性所需的數(shù)據(jù)。這個消息包含verify_data字段,它的值是握手過程中所有消息的散列值。
這些消息在連接兩端都按照各自所見的順序排列,并以協(xié)商新得到的主密鑰計算散列。這個過程是通過一個偽隨機函數(shù)( pseudorandom function, PRF)來完成的,這個函數(shù)可以生成任意數(shù)量的偽隨機數(shù)據(jù)。我將在本章的后續(xù)部分中對其進行介紹。散列函數(shù)與PRF一致,除非協(xié)商的套件指定使用其他算法。
兩端的計算方法一致,但會使用不同的標簽:客戶端使用client finished,而服務(wù)器則使用serverfinished。verify_data = PRF(master_secret, finished_label, Hash(handshake_messages))
因為Finished消息是加密的,并且它們的完整性由協(xié)商MAC算法保證,所以主動網(wǎng)絡(luò)攻擊者不能改變握手消息并對vertify_data的值造假。
理論上攻擊者也可以嘗試找到一組偽造的握手消息,得到的值與真正消息計算出的verity_data的值完全一致。這種攻擊本身就非常不容易,而且因為散列中混入了主密鑰(攻擊者不知道主密鑰),所以攻擊者根本不會嘗試。
在TLS 1.2版本中, Finished消息的長度默認是12字節(jié)( 96位),并且允許密碼套件使用更長的
長度。在此之前的版本,除了SSL 3使用36字節(jié)的定長消息,其他版本都使用12字節(jié)的定長消息。
客戶端身份驗證
盡管可以選擇對任意一端進行身份驗證,但人們幾乎都啟用了對服務(wù)器的身份驗證。
如果服務(wù)器選擇的套件不是匿名的,那么就需要在Certificate消息中跟上自己的證書。相比之下,服務(wù)器通過發(fā)送CertificateRequest消息請求對客戶端進行身份驗證。消息中列出所有可接受的客戶端證書。作為響應(yīng),客戶端發(fā)送自己的Certificate消息(使用與服務(wù)器發(fā)送證書相同的格式),并附上證書。此后,客戶端發(fā)CertificateVerify消息,證明自己擁有對應(yīng)的私鑰。
完整的握手如圖2-3所示。

只有已經(jīng)過身份驗證的服務(wù)器才被允許請求客戶端身份驗證?;谶@個原因,這個選項被稱
為相互身份驗證( mutual authentication)。
- CertificateRequest
服務(wù)器使用CertificateRequest消息請求對客戶端進行身份驗證,并將其接受的證書的公鑰和簽名算法傳送給客戶端。它也可以選擇發(fā)送一份自己接受的證書頒發(fā)機構(gòu)列表,這些機構(gòu)都用其可分辨名稱來表示:
struct {
ClientCertificateType certificate_types;
SignatureAndHashAlgorithm supported_signature_algorithms;
DistinguishedName certificate_authorities;
} CertificateRequest;
- CertificateVerify
客戶端使用CertificateVerify消息證明自己擁有的私鑰與之前發(fā)送的客戶端證書中的公鑰相對應(yīng)。消息中包含一條到這一步為止的所有握手消息的簽名:
struct {
Signature handshake_messages_signature;
} CertificateVerify;
會話恢復(fù)
完整的握手協(xié)議非常復(fù)雜,需要很多握手消息和兩次網(wǎng)絡(luò)往返才能開始發(fā)送客戶端應(yīng)用數(shù)
據(jù)。此外,握手執(zhí)行的密鑰學操作通常需要密集的CPU處理。身份驗證通常以客戶端和服務(wù)器證
書驗證(以及證書吊銷檢查)的形式完成,需要更多的工作。這其中的許多消耗都可以通過簡短
握手的方式節(jié)約下來。

最初的會話恢復(fù)機制是,在一次完整協(xié)商的連接斷開時,客戶端和服務(wù)器都會將會話的安全參數(shù)保存一段時間。
希望使用會話恢復(fù)的服務(wù)器為會話指定唯一的標識,稱為會話ID。服務(wù)器在 ServerHello消息中將會話ID發(fā)回客戶端(請參見2.2.2節(jié)中的示例)。
希望恢復(fù)早先會話的客戶端將適當?shù)臅扞D放入ClientHello消息,然后提交。服務(wù)器如果 愿意恢復(fù)會話,就將相同的會話ID放入ServerHello消息返回,接著使用之前協(xié)商的主密鑰生成 一套新的密鑰,再切換到加密模式,發(fā)送Finished消息??蛻舳耸盏綍捯鸦謴?fù)的消息以后,也 進行相同的操作。這樣的結(jié)果是握手只需要一次網(wǎng)絡(luò)往返。簡短握手如圖2-4所示。圖2-4 簡短握手,用于恢復(fù)已經(jīng)建立的會話用來替代服務(wù)器會話緩存和恢復(fù)的方案是使用會話票證( sesession ticket)。它是2006年引入 的(參見RFC 4507),隨后在2008年進行了更新(參見RFC 5077)。使用這種方式,除了所有的狀態(tài)都保持在客戶端(與HTTP Cookie的原理類似)之外,其消息流與服務(wù)器會話緩存是一樣的。
此文為18年讀《HTTPS權(quán)威指南》記錄內(nèi)容。如有錯誤,請批評指正。