
相信大家對「SSH 進機器」這句話肯定不陌生,但在寫這篇文章之前,我只知道 SSH 是由三個英文單字組成,其他的一概不知 🫠 所以今天就讓我們來了解 SSH 的魔法,以及如何更方便的用 SSH 連線吧!
說真的,SSH 是什麼
Secure Shell Protocol - 又稱 SSH,讓使用者可以透過安全的管道,操控及存取遠端的 Server。在 macOS 及 Linux 系統中都有內建 SSH,所以我們才能直接用終端機來連線至遠端的 server。
ssh username@remote-server-ip
SSH 連線的過程中,會經過五個步驟。我們可以在連線時用 -v
、-vv
和 -vvv
option 來查看連線的過程,v
越多越詳細(越囉唆)。
ssh -v username@remote-server-ip
接著就會看到 terminal 噴出一堆訊息
OpenSSH_9.0p1, LibreSSL 3.3.6
debug1: Reading configuration data /Users/cooper/.ssh/config
...
debug1: channel 1: connected to /var/tmp/fig/cooper/secure.socket port -2
接著我們來拆解每一個步驟,並對照到剛剛的訊息
1. 建立 TCP 連線

首先,Client 會和 Server 建立 TCP 連線。Client 向 Server 送出 synchronize(SYN)封包,其中包含了 Client 隨機產生的初始序號(Initial Sequence Number, ISN)。
Server 收到 SYN 後,便會將 Client 的 Sequence Number 加 1 後作為 acknowledge(ACK),再加上自己產生的 sequence number 後一起回覆給 Client,表示 Server 確實收到來自 Client 的 SYN。這個回覆通常會記作 SYN-ACK 或 SYN/ACK。
在這兩個步驟中都設有計時器,若在一定的時間中沒有收到預期的回覆,則會自動重新傳送封包。
接著 Client 收到來自 Server 的 SYN-ACK 後,也會將 Server 的 Sequence Number 加 1,作為 acknowledge(ACK)並回覆給 Server。此時 TCP three-way handshake 就完成。
我們可以在 terminal 中看到 Connection established,表示 TCP 連線已建立。
debug1: Connecting to remote-ip-address [remote-ip-address] port 22.
debug1: Connection established.
2. SSH Protocol Version Exchange
接著 Server 及 Client 互相交換 SSH Protocol 版本,確認相容性,並決定用哪一個版本的 Protocol 來溝通。
從 terminal 中,可以看到 Local 的 SSH 版本為 OpenSSH_9.0,而 Remote 的 SSH 版本為 OpenSSH_8.9p1,最後使用 OpenSSH_8.9p1 來溝通。
debug1: Local version string SSH-2.0-OpenSSH_9.0
debug1: Remote protocol version 2.0, remote software version OpenSSH_8.9p1 Ubuntu-3ubuntu0.3
debug1: compat_banner: match: OpenSSH_8.9p1 Ubuntu-3ubuntu0.3 pat OpenSSH* compat 0x04000000
3. Session Encryption Negotiation
當 SSH 版本確定後,就會開始進行 Session Encryption Negotiation。
此時,Client 及 Server 會產生暫時性的 private-public key pairs,並互相交換 public key。接著各自雙方拿著對方的 public key 及自己的 private key,透過 key exchange 演算法獨立計算出 shared secret。這個 shared secret 會被用來加密整個 SSH session 中傳輸的資料。
接著再回到 terminal 裡面瞧瞧:
debug1: kex: algorithm: [email protected]
debug1: kex: host key algorithm: ssh-ed25519
debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none
debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none
首先,最前面的「kex」表示 Key Exchange。
第一行表示 Client 及 Server 會使用 [email protected]
做為產生 shared secret 的 key exchange 演算法。第二行表示目前使用者嘗試用來登入的 host key 是由 ssh-ed25519 演算法計算出來的。最後,第三及第四行表示 Client 及 Server 雙方同意使用 [email protected]
演算法,並使用 shared secret 做為 key,加密整個 SSH session 中的訊息。
4. User Authentication
在 Client 及 server 決定好要使用哪一個 key exchange 演算法之後,便會開始進行 User Authentication。
在 terminal 裡面可以看到 server 支援的 authentication methods 為 publickey 及密碼兩種,此時我們以 publickey 來進行驗證。在 Server 接收 key 之後,使用者驗證就完成了!
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Offering public key: ...
debug1: Server accepts key: ...
Authenticated to remote-server-address ([remote-server-address]:port) using "publickey".
5. 建立 SSH tunnel
在 Server 驗證 Client 身份且產生 shared secret 之後,Client 和 Server 便可以使用 shared secret 來加密及解密封包。最後 server 開啟 shell environment,讓 client 可以透過 shell 來操作 server。而這個讓雙方互相溝通的「通道」稱為 SSH tunnel。
最後在 terminal 中,可以看到 SSH tunnel 已經建立完成。
debug1: channel 1: new [forwarded-streamlocal]
從這裡開始,所有傳輸的封包都會使用 shared secret 進行對稱式加密。整個封包可以拆解成五個部分:
- Packet Length (4 bytes) - 表示整個封包的長度,不包含 MAC 及 Packet Length 本身。如果 Payload 有經過壓縮,則會計算壓縮後的長度
- Padding length (1 byte) - 表示 Padding field 的長度
- Payload - 真正要傳輸的資料。如果有進行壓縮,則只會壓縮 Payload
- Padding field - Random bytes,會和真正的資料(payload)一起加密,讓真實資料更難被有心人士讀取。最小必須要是 4 bytes,最大則是 255 bytes
- Message Authentication Code (MAC) - 包含用來驗證的演算法,檢驗封包是否被竄改
除了 message authentication code 之外,其餘的資料都會被 shared secret 加密。
了解 SSH 是如何運作之後,接著就來看看如何更快速且更安全地與機器連線!
修改預設的 Port,避免機器被瘋狂嘗試登入

通常預設的 SSH 連線 Port 會是 22,所以機器被掃到就會被瘋狂嘗試登入!

因此,我們需要把預設的 Port 換掉!這裡會介紹兩種方法,分別是使用 iptables 及 firewalld 來設定。
iptables
iptables 是 Linux 系統中用來管理防火牆及封包過濾的指令工具。透過不同的 chain 來進行管理進出的 traffic。
- 編輯位於
/etc/ssh
的sshd_config
檔案
sudo vim /etc/ssh/sshd_config
- 找到一行寫著
Port 22
(可以按下/
並輸入22
來尋找),並將 Port 改成其他 Port,例如改成 20388
Port 20388
- 儲存並離開檔案
- 使用
systemctl
指令重新啟動 sshd 服務,並開始監聽新的 Port。
sudo systemctl restart sshd
Systemd 使用 systemctl
這個指令來管理各種系統服務。在這邊,我們用來重啟(restart
)sshd 這個服務。
sshd 是 OpenSSH daemon,會持續聽取來自 client 的 SSH 連線,並做出相對應的處理。另外可以對服務進行的操作還有停止(stop
)、啟動(start
)、檢查狀態(status
)等等。sudo systemctl restart sshd
這個指令等同於 sudo systemctl restart sshd.service
,省略了後面的 .service
。
- 新增一條規則,讓剛剛調整過的 Port 可以通過防火牆
sudo iptables -A INPUT -p tcp --dport 20388 -j ACCEPT
-A INPUT
是指我們想要 append 一個新的規則到 INPUT
Chain 當中。另外還有 FORWARD
及 OUTPUT
chain。
這些 chain 用來管理連線的進出,例如 INPUT
chain 管理所有進到 server 的封包,OUTPUT
chain 管理所有由離開系統的封包,FORWARD
chain 則管理所有「路過」系統的封包,指那些最終目的地不是該系統,只是透過系統來 routing 的封包們。這三個 chain 又稱為 filter table。在 iptables 當中的規則會由上至下的逐一比對,當條件符合時,就用該規則來處理封包,並且不會再往下繼續比對。
-p tcp
指的是將這個規則套用在透過 tcp 的連線上。--dport
定義目的地的 port,可以是單一個 port,像我們寫的 20388
,或是一個 port 的開始到結束範圍,如 20388:20340
。
-j
用來定義這些封包最後該「跳(jump)」到哪裡。iptables 預設可允許四種目標:
ACCEPT
- 接收封包,並停在這裡,不繼續往下比對其他 chain 的規則。REJECT
- 拒絕封包傳遞,並跟傳送封包的傢伙說:「我 reject 了啦哈哈」並停在這裡,不繼續往下比對其他 chain 的規則。DROP
- 什麼都不做,無聲無息的忽略這個封包,並停在這裡,不繼續往下比對其他 chain 的規則。LOG
- Log 這個封包,並往下繼續比對規則。
- 最後,用
Drop
來忽略 Port 22 的連線
sudo iptables -A INPUT -p tcp --dport 22 -j DROP
如此一來設定就完成了!如果用原先預設的 Port 22 來連線,最後應該會等到 Connection time out 的錯誤訊息。
firewalld
firewalld 透過「Zone」來定義不同信任層級,並透過策略(Policy)來管理及過濾 zone 與 zone 之間的 traffic。
Firewalld 將設定區分為 Runtime Configuration 及 Permanent Configuration,前者只在當下的 session 生效,所以在重啟或重新載入後就會消失;後者則是當下不會生效,但重啟或重新載入後會永久生效。

在安裝完 firewalld 後,預設會有九個 zones,分別是 drop
、public
、trusted
、block
、dmz
、external
、home
、internal
及 work
。
每個 zone 都有不同的設定及信任層級。舉例來說,drop
會 drop 所有進來的連線,只允許出去的連線;public
則是預設的 zone,會允許一些常見的服務,例如 http、https、ssh 等等,trusted
則會允許所有的連線。
而如果有安裝 Docker,則會自動多出一個 docker
zone,將 Docker 產生的所有 network interfaces 放入 docker
zone 當中。
接著來看怎麼使用 firewalld 來設定 Port 吧!
firewalld 設定 Port
- 允許 20388 透過 tcp 的連線,沒有帶入
zone
option 則預設為 public zone
sudo firewall-cmd --add-port=20388/tcp --permanent
--permanent
flag 將該設定標記為永久存在的設定,因此要重新載入後才會生效。如果沒有 --permanent
flag,則預設會是 runtime config。
- 重新載入 firewalld daemon
sudo firewall-cmd --reload
- 檢查 port 是否設定正確
sudo firewall-cmd --list-all
此時沒意外的話,就可以看到 port 設定好了!
public
target: default
icmp-block-inversion: no
interfaces:
sources:
services: ...
ports: 20388/tcp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
- 編輯位於
/etc/ssh
的sshd_config
檔案
sudo vim /etc/ssh/sshd_config
- 找到一行寫著
Port 22
(可以按下/
並輸入22
來尋找),並將 Port 改成 20388
Port 20388
如果想要支援多個 Port,可以在下面繼續加上去,例如:
Port 20388
Port 20400
記得要重複一次 firewalld 的設定。
如何不用輸入密碼及 IP 進入機器
完成 Port 的設定之後,接著就可以拿著熱騰騰的 Port 來快速進入機器!
- 建立 public-private key pairs,並指定檔案名稱及路徑。
-t
參數用來指定使用何種演算法來產生 keys。這裡我們把它叫做id_rsa_remote_vps
ssh-keygen -t rsa
或是用 key size 更小、更安全的 ed25519 演算法
ssh-keygen -t ed25519

- 接著到
/User/username/.ssh
這個路徑底下,就會看到剛才產生的兩個 key 檔案,分別是id_rsa_remote_vps
及id_rsa_remote_vps.pub
- 將新鮮出爐的 public key 丟到 remote server 上。可以手動將
id_rsa_remote_vps.pub
裡面的內容貼到 remote server 裡面的~/.ssh/authorized_keys
這個檔案當中,或者是使用指令。-i
後面指向剛才產生的 public key 位置
ssh-copy-id -i ~/.ssh/id_rsa_remote_vps.pub root@your-remote-server-ip
此時進到 remote server 當中,就可以在 ~/.ssh/
底下看到 authorized_keys
這個檔案

- 接著要來設定本機。在本機的
~/.ssh/
資料夾底下新增一個檔案,名稱叫做config
。如果沒有的話,可以用touch
來新增
touch ~/.ssh/config
- 編輯剛才新增的
config
,填入 remote server IP address、User、剛才設定的 Port,並將剛才新增的 SSH private key 路徑放在IdentityFile
,最後在Host
填上想要用來連接的名稱
# ~/.ssh/config
Host remote-vps
HostName your-remote-ip
User someuser
IdentityFile ~/.ssh/id_rsa_remote_vps
Port 20388
- 最後,就可以直接用名稱,透過 SSH 連線至 remote server!🥳🥳
ssh remote-vps
總結
我們一路從 SSH 如何連線,說到如何修改 Port,最後了解如何不用輸入密碼及 IP 來連線至 remote server。以後終於可以理直氣壯地說出 SSH 三個字了!
\ SSH! / \ SSH! / \ SSH! /
Reference
- RFC4252 - The Secure Shell (SSH) Authentication Protocol
- RFC4253 - The Secure Shell (SSH) Transport Layer Protocol
- How to SSH Without a Password (like a boss)
- Linux systemd 系統服務管理基礎教學與範例
- IptablesHowTo
- CentOS Linux 7 以 firewalld 指令設定防火牆規則教學
- Integration with Firewalld
- Three-Way Handshake - Computer Networks