SSH(Secure SHell)是為遠程登錄, 遠程通信等設計的安全通信協議, 由芬蘭研究員於1995年提出,其目的是用於替代非安全的Telnet、rsh、rexec等不安全的遠程Shell協議.
SSH提供身份認證,加密通信,完整性校驗以及身份認證等功能. 工程人員經常使用SSH協議登錄雲服務器等遠程計算機, FTP, RPC等協議也可以使用SSH提供的安全信道.
因為版權, 加密算法等原因目前廣為使用OpenSSH提供SSH服務.
使用SSH
OpenSSH分為客戶端和服務端, Linux和Mac OS系統一般會自帶openssh-client。
本地計算機上需要安裝客戶端, 遠程計算機上需要運行服務端。 SSH協議默認使用TCP22端口, 遠程計算機需要確保遠程計算機打開了22端口.
遠程計算機安裝服務端后可以使用sudo service ssh start
啟動, 或者使用sudo /etc/init.d/ssh start
.
啟動成功后可以使用service ssh status
或者ps -e
中查看ssh服務的狀態.
遠程主機配置完畢后, 在本地主機使用ssh user@host
命令可以用戶user的身份登錄遠程主機host, 如ssh root@mycloud.com
.
ssh服務可以使用口令驗證(password)或者使用私鑰驗證(private-key)。ssh優先使用私鑰驗證, 若未配置私鑰則要求用戶輸入口令進行驗證, 該口令為遠程計算機上賬戶的登錄口令.
客戶端在要求客戶輸入口令之前, 會先會檢查自己的knows_hosts
數據庫中(一般為~/.ssh/know_hosts
文件)是否已經包含當前服務端的密鑰指紋(ECDSA key fingerprint).
若包含則會繼續建立連接, 否則由用戶判斷是否相信當前客戶端. 若用戶不相信則中斷連接, 否則繼續建立連接, 並將當前服務端加入known_hosts
中。
用戶在判斷是否相信當前服務端時需注意, 若該服務端為真正的目標服務端, 則此后與此服務端通信過程中基本不會再受到中間人的威脅. 若發現錯誤信任了假冒的服務端, 則應盡快在known_hosts中刪除它的指紋, 避免計算機在通信時默認相信該假冒者.
配置密鑰對
ssh-keygen
是用於生成密鑰對的工具, 使用ssh-keygen生成RSA密鑰對:
$ssh-keygen -t rsa -C finley@gmail.com
Generating public/private rsa key pair.
Enter file in which to save the key (~/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ~/.ssh/id_rsa.
Your public key has been saved in ~/.ssh/id_rsa.pub.
The key fingerprint is:
上面的提示說明私鑰保存在~/.ssh/id_rsa
, 公鑰保存在~/.ssh/id_rsa.pub
. 如果必要的話也可以為私鑰設置口令.
使用man ssh-keygen
可以看到該命令的幫助, 最常用的選項有:
-q
: 以靜默方式生成-b bits
: 指定位數-t
: 指定加密方式, 包括: dsa | ecdsa | ed25519 | rsa | rsa1-N new_passphrase
: 指定新的私鑰密碼-C comment
: 添加注釋, 通常是用戶的簽名[-f output_keyfile]
: 保存密鑰的文件, 默認為~/.ssh/id_rsa
打開~/.ssh/id_rsa.pub
可以看到自己的公鑰, 將公鑰的內容添加到遠程計算機的~/.ssh/authorized_keys
中,然后重啟遠程計算機的sshd使新的配置生效。
為了保證安全,其它用戶不能擁有修改密鑰的權限,若系統檢測到其它用戶有密鑰的寫(w)權限將會拒絕使用該密鑰。因此,我們將.ssh
目錄權限設置為700
, 目錄下所有相關文件權限設置為600
。
在終端中再次使用ssh命令登錄時就不需要輸入遠程計算機的登錄口令了。
通常情況下我們只需要一對密鑰, 就像我們可以使用同一個身份證在不同的地方證明我們的身份一樣, 只不過防偽的不是難以偽造的證件而是難以猜測的私鑰.
傳輸文件
基於ssh傳輸文件是一個非常方便的功能, scp可以通過ssh通道在本地計算機和遠程計算機之間傳遞文件.
scp會先使用ssh的認證方式建立安全鏈接, 然后完成文件傳輸, 完成傳輸后立即斷開連接.
在本地計算機的終端中執行scp命令從遠程計算機下載文件:
# format : scp user@host:remote_path local_path
$ scp ubuntu@qcloud:~/1.txt 1.txt
注意在本地終端執行, 而非使用ssh命令登錄的遠程終端.
scp命令與cp命令相似,第一個參數為源地址,第二個參數為目標地址, 只不過支持遠程路徑.
在復制目錄下的所有內容時同樣需要-r
參數:
# format: scp -r user@host:remote_path local_path
$ scp -r ubuntu@qcloud:~/workspace workspace
目的地址寫為遠程地址即可將本地文件上傳到遠程計算機:
# format: scp local_path user@host:remote_path
$ scp 1.txt ubuntu@qcloud:~/1.txt
# format scp -r local_path user@host:remote_path
$ scp -r workspace ubuntu@qcloud:~/workspace
使用跳板機
出於安全原因,通常集群中只有網關機可以從外網登錄,內網中機器只能從網關機登錄。若先登錄網關機作為跳板,再從網關機上登錄目標機則非常麻煩。
我們可以使用ssh的ProxyCommand功能自動使用跳板機登錄內網:
$ ssh username@internal -o ProxyCommand='ssh username@gateway -W %h:%p'
其中,ProxyCommand是登錄跳板機的ssh指令,internal
是內網中目標機的地址。ssh username@internal
將在跳板機上執行,internal
通常為目標機的內網ip地址。
注意,這種方式要求目標機的authorized_keys
中包含本地計算機的密鑰。
配置主機別名
每次ssh登錄都需要輸入很長的指令是一件很麻煩的事情,我們可以在~/.ssh/config
文件中配置主機別名極大的簡化登錄指令:
Host gateway # 主機別名,使用`ssh gateway`命令可以直接登錄該主機
Protocol 2 # ssh協議版本
HostName example.com # 主機地址,支持IP或域名
Port 22 # ssh服務端口號
User ubuntu # 登錄用戶名
IdentityFile ~/.ssh/id_rsa # 使用的私鑰文件
Host internal # 主機別名,這是一個自動跳轉示例
Protocol 2
HostName 192.168.1.111 # 目標機的內網ip地址,通常不使用域名
Port 22 # 內網中目標機的ssh服務端口號
User ubuntu # 登錄目標機的用戶名
ProxyCommand ssh gateway -W %h:%p # 登錄跳板機的ssh指令,這里使用上一條配置的別名
IdentityFile ~/.ssh/id_rsa # 登錄跳板機的私鑰文件,該密鑰必須包含在跳板機的authorized_keys中
Host 10.10.0.* # 使用通配符的示例,登錄HostName符合通配符的主機都可以用該配置,如:`ssh ubuntu@10.10.0.1`
Port 22
User ubuntu
ProxyCommand ssh gateway -W %h:%p
端口轉發
SSH可以通過端口轉發為其它協議提供安全通信. 端口轉發中涉及四台主機:
- 本地主機LocalHost: 運行SSH客戶端的主機, 即用戶操作的主機
- 遠程主機RemoteHost: 運行SSH服務端的主機
- 源主機SrcHost: 發送請求的主機
- 目的主機DestHost: 請求要前往的主機
本地端口轉發是由ssh客戶端監聽本地端口, 並將發往該端口的通信由遠程主機發往目標:
ssh -L LocalPort:DestHost:DestPort user@RemoteHost
配置本地轉發后:
- 本地SSH客戶端將監聽LocalPort端口
- 所有發往該本地端口的請求將被通過SSH信道發往遠程主機RemoteHost
- 遠程主機上的SSH服務端會將請求轉發到目標主機DestHost上的DestPort端口
- 目標主機的響應將被RemoteHost和LocalHost依次轉發, 原路返回來源主機
遠程轉發的過程正好與本地轉發相反:
ssh -R RemotePort:DestHost:DestPort user@RemoteHost
配置遠程轉發后:
- 遠程主機RemoteHost上的SSH服務端將監聽RemoteHost上的RemotePort端口
- 所有發送到RemoteHost得RemotePort端口上的請求將通過SSH信道發往本地
- 本地主機上的SSH客戶端將請求轉發到目標主機DestHost上的DestPort端口
- 目標主機的響應將被LocalHost和RemoteHost依次轉發, 原路返回來源主機
本地轉發只能請求發往固定的目的主機和目的端口, 而動態轉發則會根據請求的目的和協議決定轉發到的目的主機和目的端口.
ssh -D LocalPort user@RemoteHost
ssh客戶端將會監聽所有發往本地LocalPort的請求, 並通過遠程主機中轉, 轉發到請求的目的地址.
該過程與本地轉發類似, 不過目的主機與目的端口是根據請求動態決定的.
安全通信原理
在進一步介紹認證過程之前, 需要先介紹一下對稱加密和非對稱加密的概念.
-
對稱加密: 加密和解密的密鑰相同. 如典型的移位密碼: C代替A,D代替B...
這里向后移兩位就是密鑰, 得知向后移兩位后可以將明文加密也可以將密文解密.
-
非對稱加密: 加密密鑰與解密密鑰不同. 通常加密密鑰公開(公鑰), 解密密鑰私有(私鑰), 且很難通過公鑰推斷私鑰.
非對稱加密方式下, 所有人都可以使用公鑰對明文進行加密, 但是只有持有私鑰的人才可以解密密文.
一般來說, 對稱加密方式的加解密速度比非對稱方式更快.
現在, 我們可以開始介紹SSH認證和安全通信的原理了.
協議協商階段
建立SSH安全鏈接的第一個階段為協議協商階段:
- 服務端監聽端口等待客戶端連接
- 客戶端發起TCP連接請求, 服務端接收到該請求后,向客戶端發送SSH協議版本信息.
- 客戶端接根據該版本信息與自己的版本,決定將要使用的SSH版本,並向服務端發送選用的SSH版本信息
- 服務端檢查是否支持客戶端的決定使用的SSH版本
至此,雙方完成協議協商。如果該過程中,客戶端或服務端發送SSH版本無法兼容,任何一方都可以斷開連接.
此時雙方建立明文信道, 均無法確認對方身份.
服務端認證階段
隨后進入服務端認證階段:
-
服務端向客戶端發送下列信息:
- 服務端身份公鑰(Host Key), 用於認證服務端身份
- 服務端通信公鑰(Server Key), 用於生成通信加密密鑰
-
客戶端根據Host Key, 查詢known_hosts或交由用戶判斷是否信任該服務端.
若信任則繼續,否則斷開連接 -
客戶端隨機生成會話密鑰(Session Key), 並使用服務端通信公鑰(Server Key)加密后發給服務端
-
服務端用自己的私鑰解密得到會話密鑰(Session Key)
本階段仍然使用明文信道, 當本階段結束時雙方都有了相同且安全的會話密鑰. 此后的通信過程中, 雙方將使用會話密鑰對稱加密進行安全通信.
客戶端認證階段
隨后進入客戶端認證階段, 此階段雙方已建立對稱加密安全信道.
-
口令方式: 客戶端提供用戶和口令,服務端進行匹配完成認證.
-
密鑰方式:
- 客戶端發起一個Public Key認證請求
- 服務端檢查是否存在請求帳號的公鑰(一般在~/.ssh/authorized_keys文件中),以及其擁有的訪問權限, 如果沒有則斷開連接.
- 服務端使用客戶端發送的公鑰對一個隨機的256位的字符串進行加密,並發送給客戶端.
- 客戶端使用私鑰對字符串進行解密,並生成一個MD5值發送給服務端
- 服務端根據原始隨機字符串生成MD5值進行匹配, 確認客戶端身份
至此, 雙方互相確認對方身份並建立加密信道, 可以正式進行安全通信。