前言
前段時間和同事討論HTTPS的工作原理,當時對這塊知識原理掌握還是靠以前看了一些博客介紹,深度不夠,正好我這位同事是密碼學專業畢業的,結合他密碼學角度對tls加解密這闡述,讓我對這塊原理有了更進一步的理解,正文開始...
今天我們討論2個話題
- TLS是如何保障數據傳輸安全的
- 中間人攻擊的原理和攻防
什么是TLS
TLS(Transport Layer Security)是新的標准,舊的標准叫SSL(Secure Sockets Layer)。
不管新舊標准,他們的目的都是同一個,那就是保護數據的安全,那...究竟是怎樣算保護呢?
TLS是如何保障數據傳輸安全的
先來說一般的情況,沒有SSL的時候,客戶端和服務器,之間有一個傳輸通道是用來傳輸各種數據,但!!!這個通道是透明的,也就是說,其他人可以清楚的看到客戶端和服務器到底在秘密的交換什么東西,而有了SSL之后,就是從原本的透明傳輸通道升級成了非透明的傳輸通道。這樣其他人就不容易的看到到底在傳輸什么東西了
這里不就說http和https的概念了,簡單來說就是http有了ssl就升級為https。
http 就是走的透明的通道
https 就是非透明的通道
沒用https會經常被網絡服務商植入廣告
沒准你也遇到過,訪問某個網站時會被植入廣告誘導點擊。
這就是有因為http是明文傳輸的,長城寬帶是知道你訪問的網站服務器下發的明文,自然就可以在明文中加點“東西”
達到他不可告人的目的!!
長城寬帶還是比較收斂的,只會在第一次訪問植入廣告,后面再訪問就不會植入了。
反正這種行為是絕對鄙視的!!!
SSL是怎么起作用的
既然明文傳輸是不行的,那么把明文進行加密傳輸唄。
就是客戶端通過加密 傳輸到 服務端解密
反之
服務端加密 傳輸到 客戶端解密
在到數據加密傳輸這一步之前,得首先建立安全連接通道!
SSL需要用到非對稱加密的公私鑰達到認證並建立連接通道。
建立安全連接通道后,在利用對稱式加密對通道里所有的數據進行加解密。
聽起來有點繞,沒關系為了要了解整個概念,必須先了解下對稱式加密和非對稱式加密
對稱式加密
假如說有一個加密的算法是【把字母往后移位k位,把位移后的結果以及k給對方】
所以當A想要和B說HI
A首先通過這個加密方法把HI這個詞,往后移2位,就變成了JK
當B收到位移數:2 以及JK。B就可以通過位移數2 往回推成HI
這里的位移數:2 就是這個對稱式加密算法的秘鑰
A和B都拿到同樣的秘鑰2,是一個對稱的概念!!
非對稱式加密
那就是A和B拿到的秘鑰是不同的。(上面的例子,A拿公鑰,B拿私鑰)
公鑰和私鑰一定是一組一對配對起來的。如果公鑰是O 私鑰是P 那么絕對OP是一組。
B先把公鑰給A B手上有私鑰
- 私鑰(是絕對不能外泄的) 公鑰是公開的
A 通過 B給的公鑰進行加密變成 xx ,B收到后用自己的私鑰把xx進行解密變成HI
如下圖:
整個SSL的建立的步驟分為3個大項目 ,粉色標記的三個大塊
- Authentication 使用非對稱加密進行對服務器下發的證書認證
- Key Exchange (這里注意當采用Diffie—Hellman算法會和RSA算法的不同點)
- Encrypted Data Transfer 利用第二步的對稱式加密,對數據傳輸進行加解密
想要更詳細了解圖中每一步請參閱
那些關於ssl-tls的二三事-九
注意:在公開網絡中比如瀏覽器訪問的站點,站點服務器不會要求客戶端發送客戶端證書,所以上圖的4和5是灰色展示
下面說一下之前我誤解的點
1. RAS和DH
都知道SSL用到了非對稱加密,包括網上文章:
網上文章這么說對於理解是有幫助的,如果你只想了解大概沒問題,但是作為程序員我們需要更加深入理解。
上面文章說的其實是針對采用RSA算法的。因為DH算法中不需要,所以不會在KeyExchange這一環節中利用非對稱加密傳輸數據!
DH算法說白話一點,就是在教你如何“安全的”告訴對方密碼而不用擔心密碼被竊聽。
以下是擷取自wiki的DH流程簡圖:
上圖中,Alice和Bob通過DH算法生成秘鑰K,
其中:
- g、p是2個非私密數據;
- a、b是私密數據;
- A是根據:g、p、a算出來的非私密數據;B是根據:g、p、b算出來的非私密數據;
- 把A從a傳到b,根據求K公式,b得到秘鑰k;a同理;
注:
- p是一個大素數。p的位數決定了攻擊者破解的難度。
- g則不需要很大,並且在一般的實踐中通常是2或者5。
2. RSA對稱加密算法中的秘鑰(master secret)是不經過網絡傳輸的
- Pre-master secret (PMS)
- Client’s random number
- Server’s random number
PMS是通過網絡傳輸的這里面用到了非對稱加密!
3. 容易被忽略的重要點:客戶端驗證服務端證書也用到了非對稱加密
一般我們會給域名申請證書,最終的證書是最終用CA機構的Root根證書簽發的。
每一次簽發的證書,都是自己的私鑰做了簽名,並放在證書里。
由於CA機構也會用Root根證書簽發一些中間證書,再由中間證書簽發的證書去簽名生成你要申請的證書.
如下圖:
服務端下發也是下發一個證書鏈的結構,瀏覽器拿到后去一步步驗證。但光有證書鏈,客戶端是不能完成證書校驗的,必須有一張根證書才能迭代完成簽名認證,也就是說客戶端必須信任根證書才能構建信任基礎,那么根證書在哪兒呢?
瀏覽器用的[可信任證書庫],
- 在 windows 平台中,微軟有專門的根證書庫。
- 在 Linux 平台中,軟件和服務一般使用 NSS 根證書庫。
- 在蘋果平台中,也有專門的根證書庫。
在windows操作系統中 打開cmd 輸入 certmgr 就可以打開
瀏覽器在收到server端發來的ssl證書信息,拿到證書后驗證其數字簽名。具體就是,根據證書上寫的CA簽發機構,在瀏覽器內置的根證書里找到對應的公鑰,用此公鑰解開數字簽名,得到摘要(digest,證書內容的hash值),據此驗證證書的合法性。
總之一句話,CA就是神一樣的存在,如果你信任神,你就應該信任這些CA。
中間人攻擊(MITM )
中間人攻擊,就是中間卡了一個人幫你和服務器進行數據交換。
這樣就代表傳輸的所有數據都被這個中間人看光光。
為什么我都用SSL了。不應該都是數據加密了嘛,中間人是如何知道的?
用Fiddler來模擬下中間人攻擊,要完成中間人攻擊需要配置
- Fiddler會在證書信任中心安裝一個它的證書
- 然后瀏覽器通過Fddler暴露的端口來訪問目標站點
首先看下正常的證書長啥樣的 如下圖:
中了中間人攻擊的證書是長啥樣的 ,如下圖
其實中間人的角色,就是充當了服務器在和你建立SSL。
所以對瀏覽器來說,這個中間人就是真正的服務器,只是瀏覽器不知情而已。
但是其實瀏覽器並不會那么笨,因為如上面我們講了瀏覽器會去【可信任證書中心】去驗證。如果遇到不存在的證書,瀏覽器就會出現以下的提示
客戶端本身就是壞人的情況下,才會在自己的【可信任證書中心】裝一個用於中間人攻擊的證書。
有什么辦法可以防止中間人攻擊 -》 SSL Pinning
什么是SSL Pinning
SSL Pinning 也叫 Certificate Pinning
而前面有提到一個概念,公鑰私鑰是一對一配對的。
所以同一組公私鑰出來的憑證,這個憑證里面的公鑰絕對是不會變的。
而 SSL Pinning 就是要把 SSL 固定起來,這個固定就是利用公鑰的特性實現的。
假設有一個APP是專門訪問baidu.com的
baidu.com證書里面的公鑰是O的話,而我app里面的代碼,已經有預先寫好O這個公鑰,
所以當我的app瀏覽 baidu.com的時候,取得證書里面的公鑰O
拿這個公鑰O和代碼寫的O對比是否一致。如果不一致就拒絕。
為啥是中間人攻擊的話,那么這2個O肯定不一致!
驗證一下就知道了,下面這段腳本可以從服務端下發的證書拿到公鑰
分別測試下 正常情況和中間人攻擊的情況
正常情況
#!/bin/bash
certs=`openssl s_client -connect $1:443 -servername $1 -showcerts </dev/null 2>/dev/null | sed -n '/Certificate chain/,/Server certificate/p'`
rest=$certs
while [[ "$rest" =~ '-----BEGIN CERTIFICATE-----' ]]
do
cert="${rest%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----"
rest=${rest#*-----END CERTIFICATE-----}
echo `echo "$cert" | grep 's:' | sed 's/.*s:\(.*\)/\1/'`
echo "$cert" | openssl x509 -pubkey -noout |
openssl rsa -pubin -outform der 2>/dev/null |
openssl dgst -sha256 -binary | openssl enc -base64
done
百度的CA證書是一個證書鏈,如下圖,分別是
//頒發者是 GlobalSign Organization Validation CA
9ncsiOH9INfRO1dZosXOLZck/Z+/ikYsRl0e+iOUmiw=
//頒發者是 DigiCert Inc
BbkOPUFIMuqBj5SBjChDvpb1ZCdk3b9ZNDWOnKRB/bo=
中間人攻擊情況
需要設置 -proxy 去模擬
#!/bin/bash
certs=`openssl s_client -proxy 127.0.0.1:8888 -connect $1:443 -servername $1 -showcerts </dev/null 2>/dev/null | sed -n '/Certificate chain/,/Server certificate/p'`
rest=$certs
while [[ "$rest" =~ '-----BEGIN CERTIFICATE-----' ]]
do
cert="${rest%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----"
rest=${rest#*-----END CERTIFICATE-----}
echo `echo "$cert" | grep 's:' | sed 's/.*s:\(.*\)/\1/'`
echo "$cert" | openssl x509 -pubkey -noout |
openssl rsa -pubin -outform der 2>/dev/null |
openssl dgst -sha256 -binary | openssl enc -base64
done
客戶端如何用代碼去實現SSL Pinning
CertificatePinner certPinner = new CertificatePinner.Builder()
.add("baidu.com",
"sha256/9ncsiOH9INfRO1dZosXOLZck/Z+/ikYsRl0e+iOUmiw=")
.build();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.certificatePinner(certPinner)
.build();