一、SSH協議簡介
我們經常會使用ssh username@hostIp
命令登陸我們的linux伺服器,如下圖所示:
我們也明白這是使用了SSH協議進行登陸,但我們想知道的是,為什麼可以使用SSH協議進行登陸,而且為什麼使用SSH就是安全的,其背後的原理是什麼?下面我們就一起來探討下這幾個話題。
當然啦!如果現在你手頭上有相關公網可訪問的雲主機,那麼請你登陸你的雲主機,然後執行grep sshd.*Failed /var/log/secure
命令看看,或許你會驚訝的發現有很多輸出日誌,你就會明白到底有多少人想嘗試登陸你的主機了。
簡單地說,SSH協議是建立在不安全的網路之上的進行遠端安全登陸的協議。它是一個協議族,其中有三個子協議,分別是:
- 1、傳輸層協議
[SSH-TRANS]
:提供伺服器驗證、完整性和保密性功能,建立在傳統的TCP/IP協議之上。 - 2、驗證協議
[SSH-USERAUTH]
:向伺服器驗證客戶端使用者,有基於使用者名稱密碼和公鑰兩種驗證方式,建立在傳輸層協議[SSH-TRANS]
之上。 - 3、連線協議
[SSH-CONNECT]
:將加密隧道複用為若干邏輯通道。它建立在驗證協議之上。
這裡不對SSH協議做更加詳細的介紹,我們可以自行百度一下。下面的寫作思路將先通過wireshire抓包分析SSH協議上述三個流程,最後將提出整個學習過程中小編遇到的問題進行探討。
二、SSH協議握手過程分析
在接著下面的內容之前,這裡有幾個概念非常重要!
- 1、
會話金鑰 key
:key是通過客戶端和伺服器之間通過諸如D-H演算法協商出來的。 - 2、
公鑰 pub key
:pub key成為伺服器主機金鑰server_host_key
,用於SSH-TRANS
傳輸協議進行伺服器驗證,說白了就是客戶端去驗證伺服器用的
SSH協議握手過程大致流程如下圖所示:
下面是小編通過wireshire工具抓的資料包,讓我們分別一步步進行分析:
- 1、TCP三次握手建立連線
我們都知道,TCP協議有個叫三次握手的過程,從上面圖中可以看出序號(647-649)即是TCP連線建立過程。
NO. | 描述 | seq | Win | ACK | 解釋 |
---|---|---|---|---|---|
647 | 第一次握手 | 0 | 8192 | 無 | seq = 0表示客戶端當前的TCP包序列號 |
648 | 第二次握手 | 0 | 14600 | 1 | seq = 0,表示伺服器端當前的TCP包序列號 ack = 1(客戶端seq + 1),表示對客戶端第 seq = 0 的TCP包進行應答 |
649 | 第三次握手 | 1 | 65536 | 1 | seq = 1,表示客戶端端當前的TCP包序列號 ack = 1(伺服器seq + 1),表示對伺服器端第 seq = 0 的TCP包進行應答 |
- 2、SSH版本協議交換
上圖序號(647-649)即是SSH版本協議交換過程。
NO. | 描述 | 解釋 |
---|---|---|
650 | 協議版本協商 | 伺服器將自己的SSH協議版本傳送到客戶端,格式為:SSH-protoversion(版本號)-softwareversion(自定義) SP(空格一個,可選) comments(註釋,可選) CR(回車) LF(換行) |
651 | 協議版本協商 | 客戶端將自己的SSH協議版本傳送到伺服器,格式為:SSH-protoversion(版本號)-softwareversion(自定義) SP(空格一個,可選) comments(註釋,可選) CR(回車符) LF(換行符) |
這一步其實沒什麼高大上的內容,就是傳送一個格式為SSH-protoversion-softwareversion SP comments CR LF
的位元組流而已。
- 3、金鑰協商key
上圖序號(652-677)即是SSH版本協議交換過程。
金鑰協商過程從客戶端和伺服器相互發出Key Exchange Init
請求開始,主要是告訴對方自己支援的相關加密演算法列表、MAC演算法列表等。
最後協商成功之後,將會生成一個對稱加密會話金鑰key
以及一個會話ID
,在這裡要特別強調,這個是對稱加密金鑰key,不要和公鑰相混淆了,公鑰和金鑰在上面開頭已經著重強調兩者的區別了,公鑰是給客戶端去驗證伺服器用的。
在這一步中,公鑰會從伺服器傳送到客戶端:
而會話金鑰是通過D-H演算法計算出來的,不會在網路上傳輸,其破解的難度取決於離散對數的破解難度,一般不會被破解的,有興趣的可以自行了解該演算法原理。
下面我將貼出Key Exchange Init
傳送的請求包資料分析
NO. | 描述 | 解釋 |
---|---|---|
1 | kex_algorithms |
金鑰交換演算法,裡邊即包含我們使用的D-H演算法,用於生成會話金鑰 |
2 | server_host_key_algorithms |
伺服器主機金鑰演算法,可以採用 ssh-rsa,ssh-dss,ecdsa-sha2-nistp256 ,有公鑰和私鑰的說法,公鑰即我們上面講到的pub key,對於公鑰私鑰的概念,可以參見understanding public key private key concepts |
3 | encryption_algorithms_client_to_server |
對稱加密演算法,常用的有aes128-cbc,3des-cbc |
4 | mac_algorithms_client_to_server |
MAC演算法,主要用於保證資料完整性 |
5 | compression_algorithms_client_to_server |
壓縮演算法 |
- 4、認證階段
上圖序號(678-680)即是SSH版本認證階段。
1、基於賬號和口令的驗證方式
客戶端將自己的
使用者名稱 + 密碼
用上面生成的會話金鑰key
進行加密之後傳送到伺服器端進行驗證,伺服器端驗證通過,則響應成功,否則在進行有限次(推薦是20次)重新認證。至於伺服器是怎麼驗證的,是否結合了會話ID小編也不清楚,網上眾說紛紜。
注意,使用者名稱和密碼是採用上面金鑰協商階段生成的會話金鑰key進行加密的,包括後面的連線會話階段所傳送的資料都是,不要認為是採用伺服器的pub key加密的2、基於公鑰和私鑰的驗證方式
這種方式也稱為免密登陸。簡單地說,就是客戶端自己生成公鑰私鑰(通常採用ssh-keygen程式生成),然後將公鑰以某種方式(通常是手動新增)儲存到伺服器
~/.ssh/authorized_keys
檔案中,以後伺服器都會接受客戶端傳過來的經過會話金鑰加密過的公鑰,然後解密得到公鑰之後和本地authorized_keys
配置的公鑰是否相等,如果是,則允許登陸。
如果你配過github或者gitlab的公鑰,其實第二種方式認證方式很好理解,因為github它們也是採用SSH協議進行程式碼克隆的,沒有配置公鑰好像是不允許克隆的。
三、相關問題
- 1、金鑰協商階段安全嗎?有沒有中間人攻擊的情況!
就我的理解,第一次總是不安全的。為什麼呢?上面說到協商過程中,伺服器會將自己的公鑰server_host_key_algorithms
傳送給客戶端,但是客戶端無法保證它拿到的公鑰就是目標伺服器所發出來的,很可能有個中間人攔截了你的請求,然後中間人發了另外一個公鑰給到你客戶端,這就不安全了!這也很好解釋了為什麼我們第一次登陸的時候,Shell終端總是會出現這個提示的原因:
這也是將確認權留給客戶端自己去判斷的一種策略。相反,如果想要更加安全,那麼我們可以採用第二種認證方式進行登陸。由於提示的是經過MD5之後的公鑰,那麼我們怎麼判斷這個值是有效的呢?請看下面第3個疑問。
- 2、傳輸協議協商出來的會話金鑰和會話ID到底有什麼作用?
會話金鑰:對稱加密演算法的金鑰,用於對通訊資料進行加解密,會話ID有點像WEB中的Session,就是用來表示每一個會話的,同時在認證階段也起判斷是否同一會話有效的作用。
- 3、既然密碼驗證登陸,那麼客戶端第一次登陸的時候如何驗證伺服器公鑰的正確性?
請你告訴小編吧!小編苦於找不到答案!
四、其他
下面是相關資料文件:
- 1、SSH RFC中文文件地址
- 2、The Secure Shell (SSH) Protocol Architecture
- 3、The Secure Shell (SSH) Transport Layer Protocol
我們可能會問了,既然有了SSH協議文件,那麼假如我們想要照著文件寫一個實現出來,那麼應該怎麼去入手呢?其實SSH中的傳輸協議[SSH-TARNS]
是基於TCP協議的,因此我們可以從建立一個最基本Java Socket套接字開始,逐步實現SSH的傳輸協議、認證協議以及連線協議。如果你對實現過程感興趣,可以研究下ganymed-ssh2-build209.jar
中的原始碼,結合SSH協議RFC文件,你就會發現,其實SSH協議中的協商和認證過程,其實都是通過Socket輸入流、輸出流的形式實現的。小編自己擼到協商階段果斷放棄,原因是不懂得演算法太多了!