https連接的前幾毫秒發生了什么
在討論這個話題之前,先提幾個問題:
為什么說https是安全的,安全在哪里?
https是使用了證書保證它的安全的么?
為什么證書需要購買?
我們先來看https要解決什么問題
一、 https解決什么問題
https要解決的問題就是中間人攻擊,什么是中間人攻擊(Man In The Middle Attack)呢?如下圖所示:
你和服務器的連接會經過一個中間人,你以為你和服務器在正常地傳輸入數據,其實這些數據都先經過了一個中間人,這個中間人可以窺探你的數據或者篡改你的數據后再發給服務器,相反也可以把服務器的數據修改了之后再發給你。而這個中間人對你是透明的,你不知道你的數據已經被人竊取或者修改了。
二、 中間人攻擊的方式
常見的有以下兩種:
1)域名污染
由于我們訪問一個域名時需要先進行域名解析,即向DNS服務器請求某個域名的IP地址。例如taobao.com我這邊解析的IP地址為:
在經過DNS的中間鏈點可能會搶答,返回給你一個錯誤的IP地址,這個IP地址就指向中間人的機器。
2)APR欺騙
廣域網的傳輸是用的IP地址,而在局域網里面是用的物理地址,例如路由器需要知道連接它的設備的物理地址它才可以把數據包發給你,它會通過一個ARP的廣播,向所有設備查詢某個IP地址的物理地址是多少,如下所示:
路由器發了一個廣播,詢問192.168.1.100的物理地址是多少,由于沒有人響應,所以它每隔1秒就重新發了個包。由于這個網絡上的所有機器都會收到這個包,所以這個時候就可以欺騙路由器:
上面的192.168.1.102就向路由器發了一個響應的包,告訴路由器它的物理地址。
三、https是應對中間人攻擊的唯一方式
在ssl的源碼里面就有一段注釋:
- /* cert is OK. This is the client side of an SSL connection.
- * Now check the name field in the cert against the desired hostname.
- * NB: This is our only defense against Man-In-The-Middle (MITM) attacks!
- */
最后一句的意思就是說使用https,是應對中間人攻擊的唯一方式。為什么這么說呢,這得從https連接的過程說起。
四、https連接的過程
如果對于一個外行人,可以這么解釋:
https連接,服務器發送它的證書給瀏覽器(客戶端),瀏覽器確認證書正確,并檢查證書中對應的主機名是否正確,如果正確則雙方加密數據后發給對方,對方再進行解密,保證數據是不透明的
但是如果這個外行人比較聰明,他可能會問你瀏覽器是怎么檢驗證書正確的,證書又是什么東西,加密后不會被中間人破解么?
首先證書是個什么東西,可以在瀏覽器上面看到證書的內容,例如我們訪問谷歌,然后點擊地址欄的小鎖:
再點擊詳情->查看證書,就可以看到整個證書的完整內容:
接下來再用一個WireShark的抓包工具,抓取整個https連接的包,并分析這些包的內容。
下面以訪問淘寶為例,打開淘寶,可以在Chrome里面看到淘寶的IP

然后打開WireShark,設定過濾條件為源IP和目的IP都為上面的IP,就可以觀察到整一個連接建立的過程:
第一步是肯定是要先建立TCP連接,這里就不說了,我們從Client Hello開始說起:
1. Client Hello
我們在wireshark里面觀察,將client hello里面客戶端發給服務器的一些重要信息羅列出來
(1)使用的TLS版本是1.2,TLS有三個版本,1.0,1.1,1.2,1.2是最新的版本,https的加密就是靠的TLS安全傳輸層協議:
(2)客戶端當前的時間和一個隨機密碼串,這個時間是距Unix元年(1970.1.1)的秒數,這里是147895117,隨機數的作用下面再提及。
(3)sessionId,會話ID,第一次連接時為0,如果有sessionId,則可以恢復會話,而不用重復握手過程:
(4)瀏覽器支持的加密組合方式:可以看到,瀏覽器一共支持22種加密組合方式,發給服務器,讓服務器選一個。具體的加密方式下文再介紹
(5)還有一個比較有趣的東西是域名:
為什么說這個比較特別呢,因為域名是工作在應用層http里的,而握手是發生在TLS還在傳輸層。在傳輸層里面就把域名信息告訴服務器,好讓服務根據域名發送相應的證書。
可以說,https = http + tls,如下圖所示:
數據傳輸還是用的http,加密用的tls。tls和ssl又是什么關系?ssl是tls的前身,ssl deprecated之后,才開始有了tls 1.0、1.1、1.2
3. Server Hello
服務器收到了Client Hello的信息后,就給瀏覽器發送了一個Server Hello的包,這個包里面有著跟Client Hello類似的消息:
(1)時間、隨機數等,注意服務器還發送了一個Session Id給瀏覽器。
(2)服務器選中的加密方式:服務器在客戶端提供的方式里面選擇了下面這種,這種加密方式也是目前很流行的一種方式:
4. Certificate證書
接著服務器發送一個證書的包過來:
在WireShark里面展開證書:
服務器總共是發了三個證書,第一個叫做*.tmall.com,第二個證書叫做GlobalSign Org.,第三個叫GlobalSign Root.這三個證書是什么關系呢?這三個證書是相互依賴的關系,在瀏覽器里面可以看出:
tmall的證書是依賴于GlobalSign Org的證書,換句話說,GlobalSign Org的證書為tmall的證書做擔保,而根證書GlobalSign Root為GlobalSign Org做擔保,形成一條依賴鏈。明白這點很重要,從技術的角度上來說,GlobalSign為tmall的證書做簽名,只要簽名驗證正確就說明tmall的證書是合法的。
在tmall的證書里面會指明它的上一級證書是啥:
現在來看下一個證書里面具體有什么內容。
除了上面提到的簽名外,每個證書還包含簽名的算法,和被簽名的證書tbsCertificate(to be signed Certificate)三部分:
這個tbsCertificate里面有什么東西呢?在WireShark里面展開可以看到,里面有申請證書時所填寫的國家、省份、城市、組織名稱:
以及證書支持的域名,可以看到taobao就在里面:
證書的有效期,可以看到這個證書如果不續費到今年年底就要到期了:
還有證書的公鑰,GlobalSign Org的公鑰為:
我們把證書的公鑰拷貝出來,它是一串270個字節的數字,16進制為540位:
- String publicKey = "3082010a0282010100c70e6c3f23937fcc70a59d20c30e533f7ec04ec29849ca47d523ef03348574c8a3022e465c0b7dc9889d4f8bf0f89c6c8c5535dbbff2b3eafbe356e74a46d91322ca36d59bc1a8e3964393f20cbce6f9e6e899c86348787f5736691a191d5ad1d47dc29cd47fe18012ae7aea88ea57d8ca0a0a3a1249a262197a0d24f737ebb473927b05239b12b5ceeb29dfa41402b901a5d4a69c436488def87efee3f51ee5fedca3a8e46631d94c25e918b9895909aee99d1c6d370f4a1e352028e2afd4218b01c445ad6e2b63ab926b610a4d20ed73ba7ccefe16b5db9f80f0d68b6cd908794a4f7865da92bcbe35f9b3c4f927804eff9652e60220e10773e95d2bbdb2f10203010001";
這個公鑰是由什么組成的呢?這是由N和e組成的:
- publicKey = (N, e)
其中N是一個大整數,由兩個質數相乘得到:
- N = p * q
e是一個冪指數。這個就涉及到非對稱加密算法,它是針對對稱加密算法來說的。什么是對稱加密算法呢?所謂對稱加密算法是說:會話雙方使用相同的加密解密方式,所以會話前需要先傳遞加密方式或者說是密鑰,而這個密鑰很可能會被中間人截取。所以后來才有了非對稱加密算法:加密和解密的方式不一樣,加密用的密鑰,而解密用的公鑰,公鑰是公開的,密鑰是不會傳播的,可能是保存在擁有視網膜掃描和荷槍實彈的警衛守護的機房當中。
第一個非對稱加密算法叫Diffie-Hellman密鑰交換算法,它是Diffie和Hellman發明的,后來1977年麻省理工的Rivest、Shamir 和 Adleman提出了一種新的非對稱加密算法并以他們的名字命名叫RSA。它的優點就在于:
加密和解密的計算非常簡單
破解十分難,只要密鑰的位數夠大,以目前的計算能力是無法破解出密鑰的
可以說,只要有計算機網絡的地方,就會有RSA。RSA加密具體是怎么進行的呢:
5. RSA加密和解密
假設發送的信息為Hello,由于Hello的ASCII編碼為:104 101 108 108 111,所以要發送的信息為:
M = 1041010108108111
即先把要發送的文本轉成ASCII編碼或者是Unicode編碼,然后進行加密:
EM = M^e % N
就是把M作e次冪,然后除以N取余數,得到EM,EM即為加密后的信息。其中(N,e)就是上文提到的公鑰。接下來將EM發送給對方,對方收到后用自己的密鑰進行解密:
M = EM^d % N
將加密的信息作d次冪,再除以N取模,(N,d)就是對方的密鑰,這樣就能夠將EM還原為M,可以證明,只要密鑰和公鑰是一一配對的,上式一定成立。不知道密鑰的人是無法破譯的,上文已提到破解密鑰是相當困難的。
接下來回到上文提到的證書的公鑰,這是一串270個字節的數字,可以拆成兩部分N和e:
灰色的數字是用來作為標志的。N是一個16進制為512位、二進制為2048位的大數字。普通的證書是1024位,2048位是一個很高安全級別,換算成10進制是617位,如果你能夠將這個617位的大整數拆成兩個質數相乘,就可以推導出GlobalSign的密鑰,也就是說你破解了GlobalSign的證書(但這是不可能的)。
e為65537,證書通常取的冪指數都為這個數字。
在證書里面知道證書使用的加密算法為RSA + SHA256,SHA是一種哈希算法,可用來檢驗證書是否被篡改過:
我們將encrypted的值拷貝出來就是證書的簽名:
- String sinature = "3ec0c71903a19be74dca101a01347ac1464c97e6e5be6d3c6677d599938a13b0db7faf2603660d4aec7056c53381b5d24c2bc0217eb78fb734874714025a0f99259c5b765c26cacff0b3c20adc9b57ea7ca63ae6a2c990837473f72c19b1d29ec575f7d7f34041a2eb744ded2dff4a2e2181979dc12f1e7511464d23d1a40b82a683df4a64d84599df0ac5999abb8946d36481cf3159b6f1e07155cf0e8125b17aba962f642e0817a896fd6c83e9e7a9aeaebfcc4adaae4df834cfeebbc95342a731f7252caa2a97796b148fd35a336476b6c23feef94d012dbfe310da3ea372043d1a396580efa7201f0f405401dff00ecd86e0dcd2f0d824b596175cb07b3d";
這個簽名是一個256個字節的數字,它是GlobalSign Org用它的密鑰對tbsCertificate做的簽名,接下來用GlobalSign Org的公鑰進行解密:
- String decode =
- new BigInteger(signature, 16) //先轉成一個大數字
- .pow(e) //再做e次冪
- .mod(new BigInteger(N) ) //再模以N
- .toString();
- System.out.println(decode);
注意在實際的計算中,不能直接e次冪,不然將是一個天文數字里的天文數字,計算量將會非常大,需要乘一次就模一次,很快就算出來了。計算出來的結果為:
這個結果又可以拆成幾部分來看:
第一個字節是00,第一個字節要比其它字節都要小,第二個字節是01,表示這是一個私有密鑰操作,中間的ff是用來填充,加大簽名的長度,加大破解難度,最后面的64個字節就是SHA哈希的值,如果證書沒有被篡改過,那么將tbsCertificate作一個SHA哈希,它的值應該是和簽名里面是完全一樣的。所以接下來我們手動計算一下tbsCertificate的哈希值和簽名里面的哈希進行對比。
這個的計算方式如下:
hash = SHA_256(DER(tbsCertificate))
注意不是將tbsCertificate直接哈希,是對它的DER編碼值進行哈希,DER是一種加密方式。DER值可以在WireShark里面導出來,證書發過來的時候就已經被DER過了:
然后再用openssl計算它的哈希值,命令為:
- openssl dgst -sha256 ~/tbsCertificate.bin
計算結果為:
即為:
- bedfc063c41b62e0438bc8c0fff669de1926b5accfb487bf3fa98b8ff216d650
和上面簽名里面的哈希值進行對比,即下面的綠色字:
可以發現:檢驗正確!這個證書沒有被篡改過,確實是tmall的證書。那么這個時候問題來了,中間人有沒有可能既篡改了證書,還能保證哈希值是對的?首先不同的字符串被SHA256哈希后的值是一樣的概率比較小,同時由于密鑰和公鑰是一一配對的,所以中間人只能把公鑰改成它的公鑰,這個公鑰是一個p * q的整數,所以他必須得滿足兩個條件,一個是要更改成一個有意義的公鑰,另一個是整個證書的內容被哈希后的值和沒改前是一樣的,滿足這兩個條件就相當困難了。
所以我們有理由相信,只有知道了GlobalSign Org密鑰的人,才能對這個證書正確地加密。也就是說,tmall的證書確實是GobalSign頒發和簽名的,我們可以用相同的方式驗證GlobalSign Org是GlobalSign Root頒發和簽名。但是我們為什么要相信Gloabal Sign呢?
如果上面提到的中間人不篡改證書,而是把整一個證書都換它自己的證書,假設叫HackSign,證書里面要帶上訪問的域名,所以HackSign里面也會帶上*.taobao.com。這個HackSign和GlobalSign有什么區別呢?為什么我們要相信GlobalSign,而不相信HackSign呢?
因為GlobalSign Root是瀏覽器或者操作系統自帶的證書,在火狐瀏覽器的證書列表里面可以看到:
GlobalSign Root是Builtin的,即內置的。我們絕對相信GlobalSign Root,同時驗證了GlobalSign Org是簽名是合法的,GlobalSign Org給tmall的證書也是合法的。所以這個就是我們的信任鏈,從GlobalSign Root一直相信到了tmall。而*.taobao.com的域名已經被證書機構注冊了,所以另外的人是不能再用*.taobao.com去注冊它的證書的。
所以證書為什么會有有效期,證書為什么要購買,從這里就可以知道原因了。
到這里,你可能又會冒出來另外一個問題,既然我不能改證書,也不能換證書,那我難道不能直接克隆你的證書放到我的機器上去,因為證書是完全公開透明的,可以在瀏覽器或者wireshark里面看到證書的完整內容。然后我再用域名污染等技術將你訪問的域名打到我的機器上,那么我跟你一模一樣的證書不也是合法的?證書不就沒用了?
這個問題之前也困擾我許久,為了回答這個問題,我們先繼續講解連接的過程。
上面已經說到瀏覽器已經驗證了證書是合法的,接下來:
6. 密鑰交換Key Exchange
上文提到服務器選擇的加密組合方式為:
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
這一串加密方式可以分成三部分:
服務器選中的密鑰交換加密方式為RSA,數據傳輸加密方式為AES,檢驗數據是否合法的算法為SHA256.
具體的密鑰交換為ECDHE_RSA,什么叫ECDHE呢?
由于證書的密鑰和公鑰(2048位)都比較大,使用ECDH 算法最大的優勢在于,在安全性強度相同的情況下,其所需的密鑰長度較短。以 DH 演算法密鑰長度 2048 位元為例。ECDH 算法的密鑰長度僅需要 224 位元即可
所以RSA是用來驗證身份然后交換密鑰的,并不是用來加密數據的,因為它太長了,計算量太大。加密數據是用的ECDHE生成的密鑰和公鑰。
在服務器發送了它的證書給瀏覽器之后,就進行了密鑰交換Server Key Exchange和Client Key Exchange:
在WireShark里面展開,可以看到它發給客戶端的公鑰:
這個公鑰只有65個字節,260位,和上面的270個字節的公鑰相比,已經短了很多。
同樣地,瀏覽器結合服務器發給它的隨機密碼(Server Hello),生成它自己的主密鑰,然后發送公鑰發給服務器:
這個公鑰也是只有65個字節
雙方交換密鑰之后,瀏覽器給服務器發了一個明文的Change Cipher Spec的包,告訴服務器我已經準備好了,可以開始傳輸數據了:
同樣地,服務器也會給瀏覽器發一個Change Cipher Spec的包:
瀏覽器給服務回了個ACK,然后就開始傳輸數據:
傳輸數據是用的http傳輸的,但是數據是加密的,沒有密鑰是沒辦法解密的:
上面已經提到服務器選擇的數據傳輸加密方式為AES,AES是一種高效的加密方式,它會使用主密鑰生成另外一把密鑰,其加密過程可見維基百科。
到此,整一個連接過程就講解完了。這個時候就可以回答上文提出的證書被克隆的問題,其實答案很簡單,因為這是沒有意義的。雙方采用RSA交換公鑰,使用的公鑰和密鑰是一一配套的,所以只要證書是對的,即公鑰是對的,對方沒辦法知道配套的密鑰是多少,所以即使證書被克隆,對方收到的數據是無法解密的。所以沒有人會取偷證書,因為是沒有意義的,因為他不知道密鑰,從這個角度來說證書可以驗證身份的合法性是可以理解的
這個連接的過程大概多久呢?
四、使用https的代價
在wireshark里面可以看到每個包的發送時間:
從最開始的Client Hello,到最后的Change Cipher Spec的包,即從4.99s到5.299秒,這個建立https連接的過程為0.3s。所以使用https是需要付出代價:
建立https需要花費時間(~0.3s)
數據需要加密和解密,占用更多的cpu
數據加密后比原信息更大,占用更多的帶寬
五、怎樣繞過https
使用ssltrip,這個工具它的實現原理是先使用arp欺騙和用戶建立連接,然后強制將用戶訪問的https替換成http。即中間人和用戶之間是http,而和服務器還是用的https。
怎樣規避這個問題:
如果經常訪問的網站是https的,某一天突然變成了http,那么很可能有問題,最直觀的就是瀏覽器地址欄的小鎖沒有了:
六、怎樣創建一個自簽名的證書
證書要么買,要么自己創建一個,可以使用openssl生成一個證書:
- openssl req -x509 -nodes -sha256 -days 365 -newkey rsa:2048 -keyout test.com.key -out test.com.crt
如上,使用sha256 + rsa 2048位,有效期為365天,輸出為證書test.com.crt和密鑰test.com.key,在生成過程中它會讓你填相關的信息:
最后會生成兩個文件,一個是證書(未被簽名),另一個是密鑰:
然后把這個證書添加到瀏覽器里面,設置為信任,瀏覽器就不會報NET::ERR_CERT_AUTHORITY_INVALID的錯誤,就可以正常使用這個證書了。
【本文是51CTO專欄作者“人人網FED”的原創稿件,轉載請通過51CTO聯系原作者獲取授權】