Linux SSH建立連線過程分析
SSH建立連線的過程主要分為下面幾個階段:
- SSH協議版本協商階段。SSH目前包括SSH1和SSH2兩個大版本。
- 金鑰和演算法協商階段,SSH支援多種加密演算法,雙方根據自己和對端支援的演算法進行協商,最終決定要使用的演算法。
- 認證階段,伺服器對客戶端進行身份驗證。
- 會話請求階段,完成認證後,客戶端會向伺服器端傳送會話請求。
- 互動會話階段,會話請求通過後,伺服器端和客戶端進行資訊的互動。
1)SSH協議版本協商階段:
- 客戶端通過TCP三次握手與伺服器的SSH埠建立TCP連線。
- 伺服器通過建立好的連線向客戶端傳送一個包含SSH版本資訊的報文,格式為“SSH-<SSH協議大版本號>.<SSH
- 客戶端收到版本號資訊後,如果伺服器使用的協議版本號低於自己的,但是客戶端能夠相容這個低版本的SSH協議,則就使用這個版本進行通訊。否則,客戶端會使用自己的版本號。
- 客戶端將自己決定使用的版本號發給伺服器,伺服器判斷客戶端使用的版本號自己是否支援,從而決定是否能夠繼續完成SSH連線。
- 如果協商成功,則進入金鑰和演算法協商階段。
2)金鑰和演算法協商階段:
- 伺服器端和客戶端分別傳送演算法協商報文給對端,報文中包含自己支援的公鑰演算法列表,加密演算法列表,MAC(Message
Authentication Code,訊息驗證碼)演算法列表,壓縮演算法列表等。
- 和版本協商階段類似,伺服器端和客戶端根據自己和對端支援的演算法來決定最終要使用的各個演算法。
- 伺服器端和客戶端利用Diffie-Hellman金鑰交換演算法,主機金鑰對等引數,生成共享金鑰和會話ID。會話金鑰用於在後續的通訊過程中兩端對傳輸的資料進行加密和解密,而會話ID用於認證過程。
3)認證階段:
- 客戶端向伺服器端傳送認證請求,請求中包含使用者名稱,認證方法,密碼或金鑰。
- 伺服器端對客戶端進行認證,如果認證失敗,則向客戶端傳送失敗訊息,其中包含可以再次認證的方法列表。
- 客戶端再次使用支援的認證方法中的一種進行認證,直到達到認證次數上限被伺服器終止連線,或者認證成功為止。
SSH支援的兩種認證方式:
|
4)會話請求階段:
- 伺服器等待客戶端請求。
- 認證完成後,客戶端想伺服器傳送會話請求。
- 伺服器處理客戶端請求,完成後,會向客戶端回覆SSH_SMSG_SUCCESS報文,雙方進入互動會話階段。如果請求未被成功處理,則伺服器返回SSH_SMSG_FAILURE報文,表示請求處理失敗或者不能識別客戶端請求。
5)互動會話階段:
- 客戶端將要執行的命令加密傳送給伺服器。
- 伺服器收到後,解密命令,執行後將結果加密返回客戶端。
- 客戶端將返回結果解密後顯示到終端上。
下面我們通過客戶端(172.31.100.107)抓包來簡單說明金鑰認證的過程:
報文1-3:可以看到前三個包是客戶端與伺服器端三次握手的過程
報文4:在建立連線後,伺服器端將自己支援的SSH版本傳送給客戶端
報文5:客戶端返回給伺服器自己要使用的SSH版本,如果伺服器端不支援這個版本,則到此就終止了SSH連線
報文6:客戶端將自己支援的公鑰演算法列表,加密演算法列表,MAC(MessageAuthentication Code,訊息驗證碼)演算法列表,壓縮演算法列表等傳送給伺服器
報文7,8:伺服器返回ACK報文
報文9:伺服器將自己支援的公鑰演算法列表,加密演算法列表,MAC(MessageAuthentication Code,訊息驗證碼)演算法列表,壓縮演算法列表等傳送給客戶端
這裡在雙方協商的原則是以客戶端支援的協議為主,客戶端支援的協議從左向右優先順序依次遞減,從優先順序高的協議開始匹配,如果客戶端支援的第一個協議,伺服器也支援,則雙方就使用這個協議,如果伺服器不支援,則在匹配第二個客戶端支援的協議,直到匹配到最後一個客戶端支援的協議,如果伺服器都不支援,則雙方協商失敗。 |
報文10:客戶端開始與伺服器進行通訊的共享金鑰的協商,由於前面使用的是SSH2.0的協議,所以這裡使用的是Diffie-Hellman-Group-Exchange-SHA演算法(關於DH-GEX-SHA演算法的原理,可以參考http://blog.csdn.net/lee244868149/article/details/51790397),在這個報文中,客戶端限制了金鑰交換引數Min,Numbers of Bits,Max
報文11:伺服器端收到客戶端DH請求後,將用於生成公鑰的P和G傳送給客戶端,P是一個大素數,滿足客戶端在報文10中的限制,G是大於1的數,不需要特別大,通常取2或者5
報文12:客戶端收到P和G後,自己生成私鑰a,並根據私鑰a計算出自己的公鑰e,將e傳送給伺服器端
報文13:伺服器收到客戶端發來的e後,根據e和伺服器的私鑰b可以計算出雙方的共享金鑰K,同時伺服器通過私鑰b計算出客戶端計算K需要的引數f,將f發給客戶端
此外,KEY DH host key為伺服器的主機公鑰,通常為RSA公鑰,KEY DH HSignature為伺服器用主機私鑰對計算出的雜湊值H進行簽名的結果。
H的計算方法為:H=hash(V_C||V_S||I_C||I_S||K_S||e||f||K)
其中的引數:
型別 |
值 |
說明 |
string |
V_C |
客戶端的初始報文(版本資訊:SSH-2.0-xxx,不含結尾的CR和LF) |
string |
V_S |
伺服器的初始報文 |
string |
I_C |
客戶端 SSH_MSG_KEX_INIT的有效載荷(不含開頭的資料長度值) |
string |
I_S |
伺服器的同上 |
string |
K_S |
主機祕鑰(dh gex reply(33)過程伺服器傳送host key (RSA公鑰)) |
mpint |
e |
客戶端DH公鑰 |
mpint |
f |
伺服器DH公鑰 |
mpint |
K |
共同DH計算結果 |
客戶端收到伺服器發來的f後,根據f和自己的私鑰可以計算出K,進而計算出H,同時客戶端會利用伺服器傳送過來的主機公鑰K_S來驗證伺服器傳送過來的H的簽名是否有效,如果有效,則客戶端在報文14中向伺服器傳送New Keys報文,表示雙方金鑰交換成功,計算出的H則作為整個會話的會話ID。
為了更直觀的理解,可以參考下面的計算過程:
後面的資料報文都使用雙方協商的共享金鑰,所以在抓包結果中就看不到裡面的資訊了,這裡說明一下後續金鑰認證的大致過程:
- 客戶端向伺服器傳送登陸要使用的IP地址和使用者名稱,伺服器識別對應的客戶端公鑰(儲存在authorized_keys中),找到該公鑰後,伺服器通過公鑰加密一段隨機字串,並使用共享金鑰加密後傳送給客戶端。
- 客戶端首先使用共享金鑰解密得到使用自己的公鑰加密的字串,再使用自己的私鑰解密得到原始字串,再通過共享金鑰加密後傳送給伺服器。
- 伺服器通過共享金鑰解密得到字串,與之前自己用公鑰加密的那個字串進行對比,如果一致,則說明客戶端的私鑰與自己的公鑰對應,認證成功,否則認證失敗。