咱們常常會使用ssh username@hostIp
命令登錄咱們的linux服務器,以下圖所示:html
咱們也明白這是使用了SSH協議進行登錄,但咱們想知道的是,爲何可使用SSH協議進行登錄,並且爲何使用SSH就是安全的,其背後的原理是什麼?下面咱們就一塊兒來探討下這幾個話題。linux
固然啦!若是如今你手頭上有相關公網可訪問的雲主機,那麼請你登錄你的雲主機,而後執行grep sshd.*Failed /var/log/secure
命令看看,或許你會驚訝的發現有不少輸出日誌,你就會明白到底有多少人想嘗試登錄你的主機了。git
簡單地說,SSH協議是創建在不安全的網絡之上的進行遠程安全登錄的協議。它是一個協議族,其中有三個子協議,分別是:github
[SSH-TRANS]
:提供服務器驗證、完整性和保密性功能,創建在傳統的TCP/IP協議之上。[SSH-USERAUTH]
:向服務器驗證客戶端用戶,有基於用戶名密碼和公鑰兩種驗證方式,創建在傳輸層協議[SSH-TRANS]
之上。[SSH-CONNECT]
:將加密隧道複用爲若干邏輯信道。它創建在驗證協議之上。這裏不對SSH協議作更加詳細的介紹,咱們能夠自行百度一下。下面的寫做思路將先經過wireshire抓包分析SSH協議上述三個流程,最後將提出整個學習過程當中小編遇到的問題進行探討。算法
在接着下面的內容以前,這裏有幾個概念很是重要!安全
會話密鑰 key
:key是經過客戶端和服務器之間經過諸如D-H算法協商出來的。公鑰 pub key
:pub key成爲服務器主機密鑰server_host_key
,用於SSH-TRANS
傳輸協議進行服務器驗證,說白了就是客戶端去驗證服務器用的SSH協議握手過程大體流程以下圖所示:服務器
下面是小編經過wireshire工具抓的數據包,讓咱們分別一步步進行分析:網絡
咱們都知道,TCP協議有個叫三次握手的過程,從上面圖中能夠看出序號(647-649)便是TCP鏈接創建過程。app
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包進行應答 |
上圖序號(647-649)便是SSH版本協議交換過程。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
的字節流而已。
上圖序號(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 |
壓縮算法 |
上圖序號(678-680)便是SSH版本認證階段。
一、基於帳號和口令的驗證方式
客戶端將本身的用戶名 + 密碼
用上面生成的會話密鑰key
進行加密以後傳送到服務器端進行驗證,服務器端驗證經過,則響應成功,不然在進行有限次(推薦是20次)從新認證。至於服務器是怎麼驗證的,是否結合了會話ID小編也不清楚,網上衆說紛紜。
注意,用戶名和密碼是採用上面密鑰協商階段生成的會話密鑰key進行加密的,包括後面的鏈接會話階段所傳送的數據都是,不要認爲是採用服務器的pub key加密的
二、基於公鑰和私鑰的驗證方式
這種方式也稱爲免密登錄。簡單地說,就是客戶端本身生成公鑰私鑰(一般採用ssh-keygen程序生成),而後將公鑰以某種方式(一般是手動添加)保存到服務器~/.ssh/authorized_keys
文件中,之後服務器都會接受客戶端傳過來的通過會話密鑰加密過的公鑰,而後解密獲得公鑰以後和本地authorized_keys
配置的公鑰是否相等,若是是,則容許登錄。
若是你配過github或者gitlab的公鑰,其實第二種方式認證方式很好理解,由於github它們也是採用SSH協議進行代碼克隆的,沒有配置公鑰好像是不容許克隆的。
就個人理解,第一次老是不安全的。爲何呢?上面說到協商過程當中,服務器會將本身的公鑰server_host_key_algorithms
發送給客戶端,可是客戶端沒法保證它拿到的公鑰就是目標服務器所發出來的,極可能有個中間人攔截了你的請求,而後中間人發了另一個公鑰給到你客戶端,這就不安全了!這也很好解釋了爲何咱們第一次登錄的時候,Shell終端老是會出現這個提示的緣由:
這也是將確認權留給客戶端本身去判斷的一種策略。相反,若是想要更加安全,那麼咱們能夠採用第二種認證方式進行登錄。因爲提示的是通過MD5以後的公鑰,那麼咱們怎麼判斷這個值是有效的呢?請看下面第3個疑問。
會話密鑰:對稱加密算法的密鑰,用於對通訊數據進行加解密,會話ID有點像WEB中的Session,就是用來表示每個會話的,同時在認證階段也起判斷是否同一會話有效的做用。
請你告訴小編吧!小編苦於找不到答案!
下面是相關資料文檔:
咱們可能會問了,既然有了SSH協議文檔,那麼假如咱們想要照着文檔寫一個實現出來,那麼應該怎麼去入手呢?其實SSH中的傳輸協議[SSH-TARNS]
是基於TCP協議的,所以咱們能夠從建立一個最基本Java Socket套接字開始,逐步實現SSH的傳輸協議、認證協議以及鏈接協議。若是你對實現過程感興趣,能夠研究下ganymed-ssh2-build209.jar
中的源碼,結合SSH協議RFC文檔,你就會發現,其實SSH協議中的協商和認證過程,其實都是經過Socket輸入流、輸出流的形式實現的。小編本身擼到協商階段果斷放棄,緣由是不懂得算法太多了!