目錄
- 什麼是 SSH 金鑰認證?
- 兩組金鑰別搞混:使用者金鑰 vs 主機金鑰
- 運作原理
- 產生出來的金鑰長什麼樣子
- 公鑰要放到哪裡?
- 檔案位置與權限總覽
- 金鑰類型怎麼選
- 快速開始:三個步驟
- SSH Config 客戶端設定
- authorized_keys 進階:限制單把金鑰的權限
- ssh-agent 與 passphrase
- 伺服器端 sshd 設定
- 除錯方法
- 規模化:SSH 憑證與雲端代管金鑰
- 常見問題
- 最佳實踐
- 總結
什麼是 SSH 金鑰認證?
SSH 金鑰認證是一種基於非對稱加密的身份驗證機制:本地保管一把私鑰,伺服器上登記對應的公鑰,登入時由客戶端用私鑰對伺服器出的隨機挑戰簽名,伺服器用公鑰驗證簽名是否成立。
相比密碼認證的差異
| 面向 | 密碼認證 | 金鑰認證 |
|---|---|---|
| 傳輸內容 | 密碼本身送到伺服器(雖有加密通道) | 只送簽名,私鑰永遠不離開本地 |
| 暴力破解 | 可對外開放的 SSH 埠持續猜測 | 無法從公鑰反推私鑰,猜測不可行 |
| 撤銷 | 改密碼,所有使用者都受影響 | 刪掉 authorized_keys 中的那一行即可 |
| 多台伺服器 | 每台一組密碼 | 同一把金鑰可登入多台 |
| 自動化 | 需把密碼寫在腳本裡 | 金鑰檔配合 agent,不必存明文密碼 |
準確地說:金鑰認證安全的關鍵不是「密碼長度不夠」,而是私鑰不會在網路上傳輸。密碼認證即使密碼很強,伺服器端仍會收到密碼原文;若伺服器被入侵,密碼就外洩了。金鑰認證下,被入侵的伺服器只拿得到公鑰。
取捨
- 私鑰檔案本身變成單點風險:檔案被複製走等同帳號被接管(除非有 passphrase)
- 換電腦、多裝置需要重新部署或搬移金鑰
- 沒有集中撤銷機制:一把金鑰散佈到 50 台機器,就要清 50 個
authorized_keys(這是後面「SSH 憑證」要解決的問題)
兩組金鑰別搞混:使用者金鑰 vs 主機金鑰
SSH 連線裡有兩組互相獨立的金鑰對,初學時最容易混淆:
| 使用者金鑰(User Key) | 主機金鑰(Host Key) | |
|---|---|---|
| 用途 | 證明「你是誰」 | 證明「伺服器是誰」 |
| 私鑰放哪 | 本地 ~/.ssh/id_ed25519 |
伺服器 /etc/ssh/ssh_host_ed25519_key |
| 公鑰放哪 | 伺服器 ~/.ssh/authorized_keys |
本地 ~/.ssh/known_hosts |
| 誰產生 | 使用者自己 ssh-keygen |
伺服器安裝 OpenSSH 時自動產生 |
| 出問題時的訊息 | Permission denied (publickey) |
REMOTE HOST IDENTIFICATION HAS CHANGED! |
一次連線的驗證是雙向的:
- 伺服器先出示主機金鑰 → 客戶端比對
known_hosts,確認沒有連到假伺服器(防中間人攻擊) - 客戶端再用使用者金鑰簽名 → 伺服器比對
authorized_keys,確認你有權限登入
💡 記憶方式:
authorized_keys是伺服器的「訪客名單」,known_hosts是你的「認得的伺服器清單」。兩個檔案裡放的都是公鑰,但方向相反。
運作原理
認證流程
三個關鍵細節
1. 私鑰是用來「簽名」,不是「解密」
常見的錯誤說法是「伺服器用公鑰加密一段資料,客戶端用私鑰解密」。現代 OpenSSH 走的是挑戰/回應簽名:伺服器出一段包含 session ID 的資料,客戶端簽名,伺服器驗簽。這個差別很重要——因為簽名內容綁定了本次連線的 session ID,攔截到的簽名無法重放到另一條連線上。
2. 客戶端會依序嘗試多把金鑰
若 ~/.ssh 下有多把金鑰、或 ssh-agent 裡載入了多把,客戶端會一把一把試。伺服器端 MaxAuthTries 預設 6,試太多次會直接被踢:
Received disconnect from ... : Too many authentication failures
解法是明確指定金鑰(見 SSH Config 的 IdentitiesOnly)。
3. 認證成功後才進入加密通道的「使用」階段
連線加密(步驟 1 的演算法協商 + 金鑰交換)在身份認證之前就完成了。所以即使認證失敗,過程中的往返也是加密的。
產生出來的金鑰長什麼樣子
ssh-keygen 產生的是兩個檔案,副檔名 .pub 的是公鑰,沒有副檔名的是私鑰。
ssh-keygen -t ed25519 -C "alice@laptop"
# 產生:
# ~/.ssh/id_ed25519 ← 私鑰
# ~/.ssh/id_ed25519.pub ← 公鑰
公鑰:永遠是「一行」,三個欄位
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJEXH0adJ11ndx2PMOYtoLevHbZ36j3fwAZRmykvFO4c alice@laptop
└─── ① ───┘ └────────────────────── ② ──────────────────────────────────────┘ └──── ③ ────┘
| 欄位 | 內容 | 說明 |
|---|---|---|
| ① 演算法 | ssh-ed25519 |
金鑰類型。RSA 是 ssh-rsa,ECDSA 是 ecdsa-sha2-nistp256 |
| ② 金鑰主體 | AAAAC3Nza... |
Base64 編碼的實際公鑰資料 |
| ③ 註解 | alice@laptop |
純標註用,改掉或刪掉都不影響認證 |
② 為什麼每把 Ed25519 公鑰開頭都一樣?
Base64 解開後是「長度前綴 + 欄位」的二進位結構,第一個欄位就是演算法名稱字串:
00000000: 0000 000b 7373 682d 6564 3235 3531 3900 ....ssh-ed25519.
└─長度 11─┘└──── "ssh-ed25519" ────┘
00000010: 0000 2091 171f 469d 275d 6777 1d8f 30e6 .. ...F.']gw..0.
└長度 32┘└──── 32 位元組的公鑰本體 ────
因為開頭固定是「11、ssh-ed25519」,Base64 之後就固定是 AAAAC3NzaC1lZDI1NTE5AAAAI。看到這串就知道是 Ed25519 公鑰。同理:
| 開頭字串 | 金鑰類型 |
|---|---|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5 |
Ed25519 |
ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB |
RSA(AAAADAQAB 是公開指數 65537) |
ecdsa-sha2-nistp256 AAAAE2VjZHNh |
ECDSA P-256 |
長度差很多:Ed25519 公鑰整行約 94 位元組,RSA-4096 公鑰約 738 位元組(將近 8 倍)。這是實務上偏好 Ed25519 的原因之一——貼到 Web 介面或 metadata 欄位時短很多。
私鑰:多行 Base64 區塊
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCRFx9GnSddZ3cdjzDmLaC3rx22d+o938AGUZspLxTuHAAAAJCuLTPeri0z
(中間省略若干行)
-----END OPENSSH PRIVATE KEY-----
從第二行就能看出有沒有設 passphrase:
| 第二行開頭 | 意義 |
|---|---|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQ |
未加密(none / none,即無加密演算法、無 KDF) |
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0 |
有 passphrase(aes256-ctr 加密 + bcrypt KDF) |
(b3BlbnNzaC1rZXktdjEA 是 openssh-key-v1\0 的 Base64,所有新格式私鑰都以此開頭。)
兩種私鑰格式:
| 開頭標記 | 格式 | 說明 |
|---|---|---|
-----BEGIN OPENSSH PRIVATE KEY----- |
OpenSSH 新格式 | 現在的預設,支援 bcrypt KDF,較耐離線暴力破解 |
-----BEGIN RSA PRIVATE KEY----- |
舊 PEM 格式 | OpenSSH 7.8 之前的預設;有加密時會出現 Proc-Type: 4,ENCRYPTED 標頭 |
舊格式可轉新格式:
ssh-keygen -p -f ~/.ssh/id_rsa # 重設 passphrase 時順便轉為新格式
指紋(Fingerprint)
指紋是公鑰的雜湊摘要,用來人工核對是不是同一把金鑰——因為要人眼比對整串 Base64 不現實。
ssh-keygen -lf ~/.ssh/id_ed25519.pub
# 256 SHA256:6nOrGsa/U8fqPaqVa570CkFPVwNlc47paczAEWtSMZw alice@laptop (ED25519)
# ↑位元數 ↑SHA-256 摘要(Base64) ↑註解 ↑類型
對私鑰檔案下同樣指令會得到相同指紋(因為指紋算的是對應的公鑰):
ssh-keygen -lf ~/.ssh/id_ed25519 # 結果與上面一致
用途:GitHub/GitLab 的金鑰列表顯示的就是這串指紋,可用來確認「我本機這把,是不是就是網站上登記的那把」。
弄丟公鑰?可以從私鑰重新導出
公鑰是從私鑰算出來的,刪掉可以重生(反之不行):
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
💡 這個指令還有個副作用很好用:若私鑰有 passphrase,它會要求輸入。忘記自己有沒有設 passphrase 時,用它測試最快。
貼公鑰時最常見的三個錯誤
# ❌ 被編輯器或聊天軟體換行切斷 → authorized_keys 必須一把金鑰一整行
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJEXH0adJ11ndx2PMOYt
oLevHbZ36j3fwAZRmykvFO4c alice@laptop
# ❌ 只複製了 Base64 主體,漏掉開頭的演算法欄位
AAAAC3NzaC1lZDI1NTE5AAAAIJEXH0adJ11ndx2PMOYtoLevHbZ36j3fwAZRmykvFO4c
# ❌ 貼到了私鑰(開頭是 BEGIN ... PRIVATE KEY)
-----BEGIN OPENSSH PRIVATE KEY-----
安全的複製方式(避免手動選取出錯):
# macOS
pbcopy < ~/.ssh/id_ed25519.pub
# Linux(X11)
xclip -sel clip < ~/.ssh/id_ed25519.pub
公鑰要放到哪裡?
這是整套機制最容易含糊帶過、卻最常出錯的地方。
核心規則:登入身分決定家目錄
ssh alice@server.example.com
# └─①─┘
① 這個使用者名稱,決定公鑰必須放進哪個家目錄。
伺服器收到「要用 alice 登入」後,會去查 alice 這個系統帳號的家目錄,再讀該目錄下的 .ssh/authorized_keys:
| 登入指令 | 伺服器實際去讀的檔案 |
|---|---|
ssh alice@host |
/home/alice/.ssh/authorized_keys |
ssh deploy@host |
/home/deploy/.ssh/authorized_keys |
ssh root@host |
/root/.ssh/authorized_keys(root 家目錄不在 /home 底下) |
ssh ubuntu@host |
/home/ubuntu/.ssh/authorized_keys(AWS Ubuntu AMI 預設帳號) |
家目錄實際位置由 /etc/passwd 決定,可直接查:
getent passwd alice | cut -d: -f6
# /home/alice
與「你在本機叫什麼名字」完全無關。本機使用者是 alice、要以 deploy 身分登入伺服器,公鑰就得進 /home/deploy/.ssh/authorized_keys。
由此推導出的三種常見情境
情境 1:一個人、多個伺服器帳號
同一把公鑰可以同時放進多個帳號的 authorized_keys:
本機 ~/.ssh/id_ed25519.pub(一把)
├──→ server-a:/home/alice/.ssh/authorized_keys → ssh alice@server-a
├──→ server-a:/home/deploy/.ssh/authorized_keys → ssh deploy@server-a
└──→ server-b:/root/.ssh/authorized_keys → ssh root@server-b
情境 2:多個人、共用一個帳號
團隊都用 deploy 帳號部署,就把每個人的公鑰各佔一行放進同一個檔案:
# /home/deploy/.ssh/authorized_keys
ssh-ed25519 AAAAC3Nza...FO4c alice@laptop
ssh-ed25519 AAAAC3Nza...9xKq bob@thinkpad
ssh-rsa AAAAB3Nza...w1Zp carol@desktop
註解欄位在這裡就很有價值——它是唯一能辨識「這行是誰的」的線索,要撤銷 Bob 的權限時才知道刪哪一行。
⚠️ 取捨:共用帳號的稽核紀錄只會顯示
deploy登入,看不出是誰。稽核要求高的環境應該一人一帳號,或改用 SSH 憑證。
情境 3:一台機器、多把不同金鑰
工作與個人金鑰分開,兩把公鑰放進同一個帳號也完全可行——伺服器只認「這行公鑰在不在名單上」。
例外:authorized_keys 不一定在家目錄
以上都是預設行為。伺服器可以改設定,此時把公鑰寫進 ~/.ssh/authorized_keys 會完全沒有效果:
# /etc/ssh/sshd_config
# 例外 1:改成集中管理目錄(%u 會替換成使用者名稱)
AuthorizedKeysFile /etc/ssh/authorized_keys/%u
# 例外 2:改由外部指令動態提供公鑰
# (OS Login、LDAP、Teleport、Vault 等集中式方案都走這條)
AuthorizedKeysCommand /usr/bin/google_authorized_keys
AuthorizedKeysCommandUser root
排查前先確認伺服器實際採用哪一種:
sudo sshd -T | grep -i authorizedkeys
# authorizedkeysfile .ssh/authorized_keys .ssh/authorized_keys2
# authorizedkeyscommand none
(sshd -T 印出實際生效的完整設定,比看設定檔可靠——設定檔可能有 Include、Match 區塊或被註解掉的行。)
雲端環境幾乎都屬於例外:GCE 由 guest agent 依 metadata 寫入 authorized_keys(或啟用 OS Login 走 AuthorizedKeysCommand),AWS EC2 由 cloud-init 在首次開機寫入預設帳號。這些環境下手動改 authorized_keys 可能在下次同步時被覆寫。
檔案位置與權限總覽
客戶端(本地電腦)
~/.ssh/
├── id_ed25519 # 私鑰(600)
├── id_ed25519.pub # 公鑰(644)
├── config # 客戶端設定檔(600)
├── known_hosts # 連過的伺服器主機公鑰(644)
└── authorized_keys # 只在本機也當伺服器時才需要
伺服器端
/home/alice/ # 家目錄(755 或更嚴,不可 group/other 可寫)
└── .ssh/ # 700
└── authorized_keys # 600
/etc/ssh/
├── sshd_config # 伺服器設定
├── ssh_host_ed25519_key # 主機私鑰(600)
└── ssh_host_ed25519_key.pub # 主機公鑰(644)
權限一覽
| 路徑 | 權限 | 為什麼 |
|---|---|---|
~/.ssh |
700 |
只有自己能列出/修改內容 |
私鑰 id_* |
600 |
權限過鬆時 ssh 會直接拒絕使用 |
公鑰 *.pub |
644 |
可公開 |
authorized_keys |
600 |
別人可寫 = 別人能加自己的公鑰進來 |
| 家目錄本身 | 不可被 group/other 寫入 | 見下方 StrictModes |
StrictModes:最常被忽略的一關
sshd 預設 StrictModes yes,會在讀 authorized_keys 之前檢查:家目錄、.ssh 目錄、authorized_keys 都不可以被「擁有者以外的人」寫入。任何一層太鬆,sshd 就當作沒有這個檔案,直接回 Permission denied (publickey)——而且不會告訴你原因。
# ❌ 典型踩雷:家目錄開了群組寫入權限
drwxrwxr-x alice alice /home/alice # group 可寫 → 認證失敗
# ✅ 一次修好整條路徑
chmod 755 /home/alice
chmod 700 /home/alice/.ssh
chmod 600 /home/alice/.ssh/authorized_keys
chown -R alice:alice /home/alice/.ssh
chown 那行同樣關鍵:用 sudo 建立 .ssh 目錄時,很容易變成 root 擁有,sshd 一樣會拒絕。
金鑰類型怎麼選
# ✅ 現在的預設選擇
ssh-keygen -t ed25519 -C "alice@laptop"
# ✅ 需要相容非常舊的系統時
ssh-keygen -t rsa -b 4096 -C "alice@laptop"
| 類型 | 建議 | 說明 |
|---|---|---|
| Ed25519 | ⭐ 首選 | 速度快、金鑰短、安全強度足夠。OpenSSH 6.5(2014)起支援 |
| RSA 4096 | 相容備案 | 需支援老舊設備/堡壘機時使用。-b 2048 是目前的下限,新建議用 4096 |
| ECDSA | 不建議新增 | 沒有明顯優於 Ed25519 的理由 |
| DSA | ❌ 已淘汰 | 固定 1024 位元,新版 OpenSSH 已移除支援 |
注意 -b 對 Ed25519 無效:Ed25519 金鑰長度固定 256 位元,-b 4096 會被忽略。
ssh-rsa 被停用 ≠ RSA 金鑰不能用:OpenSSH 8.8 起預設停用的是 SHA-1 簽名演算法(名稱剛好也叫 ssh-rsa),不是 RSA 金鑰本身。同一把 RSA 金鑰改用 rsa-sha2-256 / rsa-sha2-512 簽名仍可正常運作,只有非常舊的伺服器才會出問題。
加強 passphrase 保護(新格式私鑰的 KDF 迭代次數,預設 16):
ssh-keygen -t ed25519 -a 100 -C "alice@laptop"
快速開始:三個步驟
步驟 1:產生金鑰對
ssh-keygen -t ed25519 -C "alice@laptop"
互動提示:
Enter file in which to save the key (/home/alice/.ssh/id_ed25519): ← Enter 用預設
Enter passphrase (empty for no passphrase): ← 建議設定
Enter same passphrase again:
輸出:
Your identification has been saved in /home/alice/.ssh/id_ed25519
Your public key has been saved in /home/alice/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:6nOrGsa/U8fqPaqVa570CkFPVwNlc47paczAEWtSMZw alice@laptop
用 -f 可指定檔名(多把金鑰時必要):
ssh-keygen -t ed25519 -C "work" -f ~/.ssh/id_ed25519_work
步驟 2:把公鑰放到目標帳號
# ✅ 方法一:ssh-copy-id(推薦,會自動處理目錄與權限)
ssh-copy-id -i ~/.ssh/id_ed25519.pub alice@server
# 方法二:手動(注意權限,這是 ssh-copy-id 幫你做掉的部分)
cat ~/.ssh/id_ed25519.pub | ssh alice@server \
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
⚠️ 手動方法用
>>(附加)而非>(覆寫)。用錯會清掉該帳號原有的所有金鑰,包括自己現在正在用的那把。
沒有密碼可登入時(例如雲端 VM),改從平台的 Console/metadata 介面貼入公鑰。
步驟 3:驗證
ssh alice@server
# 不需輸入伺服器密碼即代表成功(若金鑰有 passphrase,會問 passphrase,那是本地解鎖,不是伺服器密碼)
分不清「問的是 passphrase 還是伺服器密碼」時看提示字:
Enter passphrase for key '/home/alice/.ssh/id_ed25519': ← 本地私鑰解鎖,金鑰認證有生效
alice@server's password: ← 伺服器密碼,代表金鑰認證沒過
SSH Config 客戶端設定
~/.ssh/config 讓連線參數固定下來,不必每次打長指令。
# ~/.ssh/config
# 全域預設(放最前面;SSH 採「先出現者優先」)
Host *
AddKeysToAgent yes
ServerAliveInterval 60
ServerAliveCountMax 3
# 生產環境
Host prod
HostName 192.168.1.100
User admin
Port 2222
IdentityFile ~/.ssh/id_ed25519_prod
IdentitiesOnly yes
# 經跳板機連內網機器
Host internal
HostName 10.0.0.50
User admin
IdentityFile ~/.ssh/id_ed25519_work
ProxyJump bastion
Host bastion
HostName bastion.company.com
User bastion-user
IdentityFile ~/.ssh/id_ed25519_work
# 多個 GitHub 帳號:用不同 Host 別名區分
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
使用:
ssh prod
ssh internal # 自動經 bastion 跳轉
git clone git@github-work:company/repo.git # 走工作金鑰
git clone git@github-personal:alice/side-project.git # 走個人金鑰
常用選項
| 選項 | 說明 |
|---|---|
HostName |
實際主機位址(Host 只是別名) |
User |
登入身分——同時決定伺服器讀哪個家目錄 |
IdentityFile |
指定私鑰路徑 |
IdentitiesOnly |
只用指定的金鑰,不把 agent 裡其他金鑰全丟出去試 |
Port |
SSH 埠 |
ProxyJump |
跳板機(取代舊的 ProxyCommand ssh -W) |
AddKeysToAgent |
首次使用後自動加入 ssh-agent |
ServerAliveInterval |
閒置保活間隔(秒),避免 NAT/防火牆切斷 |
ForwardAgent |
轉發 agent——有風險,見下方 |
IdentitiesOnly yes 為什麼重要
沒有它時,客戶端會把 agent 裡所有金鑰依序送出去試。金鑰一多就撞上伺服器的 MaxAuthTries(預設 6),出現 Too many authentication failures——即使正確的那把就在清單裡,只是排太後面。
多把金鑰的環境建議每個 Host 都加上 IdentitiesOnly yes。
ForwardAgent 的風險
ForwardAgent yes 讓遠端主機能透過你的 agent 使用你的私鑰簽名。若該主機被入侵,攻擊者在你連線期間可以冒用你的身分登入任何你有權限的機器(私鑰本身不會外流,但簽名能力等同借出去了)。
# ❌ 避免全域開啟
Host *
ForwardAgent yes
# ✅ 改用 ProxyJump——跳板機不會拿到你的簽名能力
Host internal
ProxyJump bastion
確認實際生效的設定:
ssh -G prod | grep -iE "hostname|user|identityfile|port"
authorized_keys 進階:限制單把金鑰的權限
authorized_keys 每一行可在公鑰前面加上選項,限制該把金鑰能做什麼。這是自動化場景的重要工具。
# 一般:完整登入權限
ssh-ed25519 AAAAC3Nza...FO4c alice@laptop
# 限制來源 IP
from="203.0.113.10,10.0.0.0/8" ssh-ed25519 AAAAC3Nza...FO4c ci-runner
# 強制只能執行特定指令(無論客戶端下什麼指令)
command="/usr/local/bin/backup.sh",restrict ssh-ed25519 AAAAC3Nza...FO4c backup-job
# 只做 port forwarding,不給 shell
restrict,permitopen="127.0.0.1:5432" ssh-ed25519 AAAAC3Nza...FO4c db-tunnel
| 選項 | 作用 |
|---|---|
restrict |
關閉所有轉發與 pty(OpenSSH 7.2+);建議作為預設起點,再逐項放行 |
command="..." |
強制執行指定指令,忽略客戶端要求 |
from="pattern" |
限制來源位址 |
permitopen="host:port" |
允許轉發到特定目標 |
no-agent-forwarding |
禁止 agent 轉發 |
expiry-time="20261231" |
金鑰到期時間(OpenSSH 8.2+) |
典型用途:CI 部署金鑰只允許執行部署腳本、備份主機只能拉取資料、資料庫 tunnel 帳號拿不到 shell。相較「給完整權限再靠應用層自律」,這是在 sshd 層就擋掉。
💡
command=搭配restrict是最小權限原則的實作:即使私鑰外洩,攻擊者也只能觸發那支腳本。
ssh-agent 與 passphrase
passphrase 是什麼
passphrase 是用來加密「私鑰檔案本身」的密碼,保護對象是本機磁碟上的那個檔案,與伺服器無關。
passphrase ──保護──> ~/.ssh/id_ed25519(私鑰檔案)
私鑰 ──證明──> 伺服器上某個帳號的登入權
最常見的混淆是把它和伺服器密碼當成同一件事:
| 保護什麼 | 會不會送到伺服器 | |
|---|---|---|
| 伺服器密碼 | 帳號的登入權 | 會,送到伺服器驗證 |
| passphrase | 磁碟上的私鑰檔案 | 不會,永遠留在本機 |
設了 passphrase 之後,私鑰在磁碟上是加密狀態(即前面 金鑰長相 提到的 aes256-ctr / bcrypt 標記)。需要簽名時,ssh 先用 passphrase 在記憶體中解開私鑰再簽——伺服器不會知道、也不需要知道你的私鑰有沒有加密。
為什麼要設:私鑰檔案等同帳號存取權。筆電遺失、備份外洩、~/.ssh 誤 commit 進 Git——這些情況下 passphrase 是最後一道防線;沒設的話,拿到檔案的人可直接登入所有部署過該公鑰的機器。
命名是有意的:passphrase 面對的是「攻擊者已取得檔案」的離線暴力破解,沒有伺服器限制嘗試次數,可用 GPU 全速猜測。因此長度比符號複雜度更重要,一個好記的長詞組優於 P@ssw0rd!。ssh-keygen -a 100 提高 KDF 迭代次數,就是在拉高每次猜測的成本。
# 幫既有金鑰加上/變更/移除 passphrase
ssh-keygen -p -f ~/.ssh/id_ed25519
# 忘記有沒有設?有設就會要求輸入
ssh-keygen -y -f ~/.ssh/id_ed25519
💡
ssh-keygen -p只是重新加密同一把私鑰,金鑰對本身不變,因此伺服器上的authorized_keys完全不用動,也不需重新部署公鑰。
用 ssh-agent 免去重複輸入
金鑰設了 passphrase,每次連線都要輸入很煩;ssh-agent 把解鎖後的私鑰留在記憶體中,一個工作階段只需輸入一次。
這兩件事是配套的:只有 passphrase 而沒有 agent,體驗差到多數人乾脆不設 passphrase,安全性等於歸零。
# 啟動 agent(多數桌面環境已自動啟動)
eval "$(ssh-agent -s)"
# 載入私鑰(此時輸入 passphrase)
ssh-add ~/.ssh/id_ed25519
# 查看已載入的金鑰
ssh-add -l
# 清空(離開座位或工作結束時)
ssh-add -D
macOS:存進 Keychain 免重複輸入
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
搭配 ~/.ssh/config 讓它每次自動載入:
Host *
UseKeychain yes
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
Linux:設定有效期限
# 載入後 8 小時自動移除
ssh-add -t 8h ~/.ssh/id_ed25519
⚠️ 在
~/.bashrc無條件寫eval "$(ssh-agent -s)"會讓每開一個終端機就多一個 agent 行程。多數發行版的桌面工作階段已提供 agent,先確認echo $SSH_AUTH_SOCK有值再說。
沒有 passphrase 的金鑰算不算安全?
取捨很明確:
- 有 passphrase:私鑰檔案被複製走也不能直接用,攻擊者還要破 passphrase
- 無 passphrase:檔案 = 存取權,但可用於無人值守的自動化
實務折衷:人用的金鑰一律設 passphrase(配合 agent,體驗差異不大);自動化用的金鑰可不設,但要用 from= / command= 限制權限範圍,並縮小該金鑰能碰到的機器。
伺服器端 sshd 設定
設定檔位於 /etc/ssh/sshd_config(部分發行版會有 /etc/ssh/sshd_config.d/*.conf 覆寫,注意 Include 的順序)。
# 啟用公鑰認證(預設就是 yes)
PubkeyAuthentication yes
# 確認金鑰認證可用「之後」再關密碼登入
PasswordAuthentication no
KbdInteractiveAuthentication no
# 禁止 root 直接登入;若必須保留,用 prohibit-password(只允許金鑰)
PermitRootLogin no
# 限定可登入的帳號
AllowUsers alice deploy
# 或用群組
AllowGroups ssh-users
# 權限檢查(預設 yes,不要關)
StrictModes yes
# 認證嘗試次數
MaxAuthTries 6
套用流程——順序很重要:
# 1. 先驗證語法,語法錯誤會導致 sshd 起不來
sudo sshd -t
# 2. 確認實際生效值符合預期
sudo sshd -T | grep -iE "passwordauthentication|permitrootlogin|pubkeyauthentication"
# 3. 重新載入
sudo systemctl reload sshd # 部分發行版是 ssh.service
🚨 保留一條退路:改動認證相關設定時,保持目前這條 SSH 連線不要關,另開一個新視窗測試登入。
reload不會中斷既有連線,萬一設定有問題還能改回來。雲端 VM 則確認 Console/序列埠登入可用。
除錯方法
客戶端:ssh -vvv
ssh -vvv alice@server
在大量輸出中重點看這幾行:
debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:6nOr...
→ 客戶端送出了這把金鑰
debug1: Server accepts key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:6nOr...
→ 伺服器認得,接下來會要求簽名
debug1: Authentications that can continue: publickey,password
→ 這把被拒絕了,還可以再試其他方式
debug1: No more authentication methods to try.
Permission denied (publickey).
→ 所有金鑰都被拒
判斷方向:
| 現象 | 指向 |
|---|---|
完全沒有 Offering public key |
客戶端根本沒送金鑰 → IdentityFile 路徑錯或檔案不存在 |
有 Offering 但沒有 Server accepts |
伺服器名單裡沒這把,或權限/StrictModes 擋掉 |
Too many authentication failures |
金鑰太多把 → 加 IdentitiesOnly yes |
卡在 Connecting to ... |
網路/防火牆/埠號問題,還沒到認證階段 |
伺服器端:看 sshd 日誌
客戶端訊息刻意含糊(避免洩露帳號是否存在),真正的原因在伺服器日誌:
# systemd 系統
sudo journalctl -u sshd -f
# Debian / Ubuntu
sudo tail -f /var/log/auth.log
# RHEL / CentOS / Rocky
sudo tail -f /var/log/secure
典型訊息與含義:
| 日誌訊息 | 原因 |
|---|---|
Authentication refused: bad ownership or modes for directory /home/alice |
StrictModes 擋下,權限/擁有者不對 |
Invalid user alice from ... |
伺服器上沒有這個帳號 |
Connection closed by authenticating user alice ... [preauth] |
客戶端試完所有金鑰都失敗 |
User alice not allowed because not listed in AllowUsers |
被 AllowUsers 擋下 |
提高日誌詳細程度
# /etc/ssh/sshd_config
LogLevel VERBOSE # 會記錄用了哪把金鑰的指紋,便於追查是誰登入
VERBOSE 會把成功登入所用的金鑰指紋寫進日誌,是共用帳號情境下唯一能追出「這次是誰登入」的方法。
規模化:SSH 憑證與雲端代管金鑰
authorized_keys 的根本限制是沒有集中撤銷點:一把金鑰散到 50 台機器,撤銷就得清 50 個檔案。機器數量上去之後有兩條路。
方案 1:SSH 憑證(SSH Certificate)
引入一個 CA,由 CA 簽發有期限的使用者憑證;伺服器只需信任 CA,不必逐台登記個人公鑰。
# CA 簽發憑證給 alice,有效期 8 小時
ssh-keygen -s ca_key -I "alice@company" -n alice -V +8h alice_key.pub
# 產生 alice_key-cert.pub
# 伺服器端:/etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca.pub
好處:新人上線不必逐台部署公鑰;離職時停止簽發即可,既有憑證也會自然過期。代價是要維運 CA 與簽發流程。
方案 2:雲端平台代管
主流雲端已把這層做掉:
| 平台 | 機制 |
|---|---|
| GCE | Guest Agent 依 metadata 的 ssh-keys 寫入 authorized_keys;啟用 OS Login 則改走 AuthorizedKeysCommand,權限由 IAM 控制 |
| AWS EC2 | cloud-init 於首次開機把 key pair 公鑰寫入預設帳號;或改用 EC2 Instance Connect / SSM Session Manager |
| Azure | VM Agent 佈建公鑰;或用 AAD 登入擴充功能 |
共同注意事項:這些環境下手動改 authorized_keys 可能被平台的同步機制覆寫,應該從平台的 metadata/IAM 層面操作。
常見問題
問題 1:Permission denied (publickey)
最常見的錯誤,按順序排查:
# 1. 客戶端到底送出了哪把金鑰?
ssh -v alice@server 2>&1 | grep -i "offering\|accepts"
# 2. 伺服器上那個帳號的名單裡有沒有這把?(用還能登入的方式進去查)
cat ~/.ssh/authorized_keys
# 3. 比對指紋是否一致
ssh-keygen -lf ~/.ssh/id_ed25519.pub
# 4. 檢查權限鏈(StrictModes)
ls -ld ~ ~/.ssh ~/.ssh/authorized_keys
# 5. 看伺服器日誌拿到真正的原因
sudo journalctl -u sshd -n 50
依經驗,成因分布大致是:公鑰放錯帳號的家目錄 > 權限/擁有者不對(StrictModes) > 送錯金鑰 > 伺服器改用 AuthorizedKeysCommand。
問題 2:REMOTE HOST IDENTIFICATION HAS CHANGED!
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
這是主機金鑰對不上,不是你的金鑰有問題。 兩種可能:
- 伺服器重灌/重建,主機金鑰重新產生了(常見且無害)
- 真的遭遇中間人攻擊(少見但不能排除)
確認是情況 1 之後才清除舊記錄:
ssh-keygen -R server.example.com # 移除該主機的舊記錄
ssh-keygen -R 192.168.1.100 # IP 也要清(known_hosts 兩者分開記)
⚠️ 不要養成無腦
StrictHostKeyChecking=no的習慣——那等於永久放棄中間人攻擊的防護。
問題 3:首次連線的指紋確認要怎麼處理?
The authenticity of host 'server (192.168.1.100)' can't be established.
ED25519 key fingerprint is SHA256:xxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
正確做法是事先取得指紋來比對(向管理員索取,或從雲端 Console 的序列埠輸出讀取),而非直接按 yes。
在伺服器上查它自己的指紋:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
批次部署可預先寫入 known_hosts:
ssh-keyscan -t ed25519 server.example.com >> ~/.ssh/known_hosts
(ssh-keyscan 本身沒有驗證能力,它只是「先抓下來」;在不可信網路上仍需另外核對指紋。)
問題 4:如何撤銷某把金鑰的存取權限?
# 1. 找出目標行(用註解欄位辨識)
grep -n "bob@thinkpad" ~/.ssh/authorized_keys
# 2. 刪除該行
sed -i.bak '/bob@thinkpad/d' ~/.ssh/authorized_keys
# 3. 確認結果
cat ~/.ssh/authorized_keys
立即生效,但不會中斷已建立的連線。金鑰疑似外洩時要一併踢掉現有 session:
who # 找出登入中的 session
sudo pkill -u bob # 或針對特定 pts 終止
而且要記得:這只清了這一台。金鑰外洩時必須逐台清理,或用 grep 掃過所有主機。
問題 5:換新電腦,金鑰要怎麼搬?
兩種做法:
# 做法 A:產生新金鑰(較安全,推薦)
# 1. 新電腦上產生新金鑰對
ssh-keygen -t ed25519 -C "alice@new-laptop"
# 2. 從舊電腦把新公鑰部署到各伺服器
ssh-copy-id -i ~/.ssh/id_ed25519_new.pub alice@server
# 3. 確認新金鑰可用後,從各伺服器移除舊公鑰
# 做法 B:搬移現有私鑰(方便但風險較高)
# 用加密方式傳輸,絕不透過 email/雲端硬碟/聊天軟體明文傳送
scp ~/.ssh/id_ed25519 new-machine:~/.ssh/
chmod 600 ~/.ssh/id_ed25519 # 在新機器上修正權限
做法 A 的好處是舊電腦遺失時,撤銷舊公鑰即可,不影響新電腦。
問題 6:Too many authentication failures
金鑰太多,還沒試到正確那把就超過 MaxAuthTries。
# 臨時解法
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519_prod alice@server
# 根本解法:寫進 ~/.ssh/config
Host prod
IdentityFile ~/.ssh/id_ed25519_prod
IdentitiesOnly yes
最佳實踐
1. 用 Ed25519,人用的金鑰設 passphrase
# ✅
ssh-keygen -t ed25519 -a 100 -C "alice@laptop"
# ❌ 沿用舊習慣產生 RSA 2048、且不設 passphrase
ssh-keygen -t rsa
2. 依用途分開金鑰,檔名要能辨識
# ✅ 看檔名就知道用途,撤銷時不會誤刪
~/.ssh/id_ed25519_work
~/.ssh/id_ed25519_personal
~/.ssh/id_ed25519_aws_prod
# ❌ 一把金鑰打天下:任何一處外洩,全部機器都要換
~/.ssh/id_rsa
分開的實際好處是爆炸半徑可控:GitHub 用的金鑰外洩,不會連帶影響生產伺服器。
3. 每個 Host 都加 IdentitiesOnly yes
避免把所有金鑰丟給對方試——既會撞上 MaxAuthTries,也等於向對方揭露你持有哪些金鑰。
4. 自動化金鑰用 restrict + command= 限制
# ✅ CI 部署金鑰只能觸發部署腳本
command="/usr/local/bin/deploy.sh",restrict,from="203.0.113.0/24" ssh-ed25519 AAAA... ci-deploy
# ❌ 給 CI 一把完整權限的金鑰
ssh-ed25519 AAAA... ci-deploy
5. 關閉密碼登入前,先確認金鑰可用
# ✅ 順序
# 1. 部署金鑰 → 2. 新開視窗測試登入成功 → 3. 才改 PasswordAuthentication no → 4. reload
# ❌ 先關密碼登入再測試 → 金鑰若有問題就把自己鎖在門外
6. 伺服器端開 LogLevel VERBOSE
共用帳號時,這是唯一能從日誌追出「哪把金鑰登入」的方式。
7. 私鑰備份要加密
# ✅ 加密後再存放
tar czf - ~/.ssh/id_ed25519 | gpg -c > ssh-key-backup.tar.gz.gpg
# ❌ 絕不要做的事
# - commit 進 Git(即使是私有 repo)
# - 存到未加密的雲端硬碟
# - 用 email/聊天軟體傳送
# - 放進容器映像檔或 CI 環境變數的明文
8. 機器數量上去就改用憑證或平台代管
authorized_keys 在十台以內好管,上百台之後撤銷成本會變成實際的安全漏洞。
總結
核心要點
- SSH 金鑰認證的本質是挑戰/簽名驗證:私鑰簽名、公鑰驗簽,私鑰永遠不離開本地
- 一次連線有兩組金鑰:主機金鑰(伺服器證明自己,對應
known_hosts)與使用者金鑰(你證明自己,對應authorized_keys) - 公鑰的落點由「登入身分」決定:
ssh alice@host→~alice/.ssh/authorized_keys;root 是/root/.ssh/;雲端環境則多由平台代寫 - 公鑰是一整行(演算法 + Base64 + 註解),私鑰是
BEGIN OPENSSH PRIVATE KEY區塊;從私鑰第二行可看出有沒有設 passphrase - 認證失敗第一順位懷疑:公鑰放錯帳號、權限/擁有者不對(StrictModes),真正原因在伺服器日誌而非客戶端訊息
快速參考
基本操作
ssh-keygen -t ed25519 -C "alice@laptop" # 產生金鑰
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host # 部署公鑰
ssh-keygen -lf ~/.ssh/id_ed25519.pub # 看指紋
ssh-keygen -y -f ~/.ssh/id_ed25519 # 從私鑰導出公鑰
ssh -i ~/.ssh/id_ed25519 user@host # 指定金鑰連線
ssh -vvv user@host # 除錯
ssh -G host # 看實際生效的客戶端設定
sudo sshd -T # 看實際生效的伺服器設定
ssh-keygen -R host # 清除舊主機金鑰記錄
檔案角色
| 檔案 | 位置 | 內容 |
|---|---|---|
id_ed25519 |
客戶端 | 私鑰,絕不外流 |
id_ed25519.pub |
客戶端 | 公鑰,用來部署 |
authorized_keys |
伺服器(登入帳號的家目錄) | 允許登入的公鑰清單 |
known_hosts |
客戶端 | 認得的伺服器主機公鑰 |
config |
客戶端 | 連線別名與參數 |
sshd_config |
伺服器 /etc/ssh/ |
伺服器認證政策 |
ssh_host_*_key |
伺服器 /etc/ssh/ |
主機金鑰對 |
權限
| 路徑 | 權限 |
|---|---|
| 家目錄 | 不可 group/other 可寫 |
~/.ssh |
700 |
| 私鑰 | 600 |
| 公鑰 | 644 |
authorized_keys |
600 |
一句話原則
私鑰留在本機,公鑰進「要登入的那個帳號」的家目錄,權限太鬆一切免談。
建立日期:2025-10-28 最後更新:2026-07-28