工程

SSH 主機金鑰 TOFU 與你的行動客戶端為什麼該在意

一份關於 Trust On First Use(TOFU)主機金鑰驗證的實用指南,並說明大多數行動 SSH 應用做錯了什麼。

CC Chen Chen· 創辦人·2026 年 4 月 30 日·約 10 分鐘

TOFU 到底是什麼意思

Trust On First Use(首次連線信任)是一種安全模型:第一次連線到某台伺服器時,你把它的識別資訊記下來。從那之後,任何以不同識別資訊回應的連線都值得懷疑。

對 SSH 而言,這個識別資訊就是伺服器的公開主機金鑰——通常是 Ed25519 或 RSA 指紋。第一次 SSH 連到 prod-server.example.com 時,伺服器會出示它的公鑰。你要嘛信任(因為這台是你架設的),要嘛不信任(不是你架的,有人在冒充)。一旦接受,指紋就會被寫入你的 known_hosts 檔案。未來的連線必須出示同一把金鑰,否則 OpenSSH 會直接拒絕繼續。

TOFU 並不完美——第一次連線是未經驗證的——但它務實。握過一次手之後,即使在惡意網路裡你也能擋掉主動中間人攻擊。

為什麼這件事在手機上更重要

手機一天裡會在多個可信與不可信網路之間來回切換:家裡 Wi-Fi、辦公室 Wi-Fi、行動網路、飯店那個一看就有問題的強制入口頁、咖啡店裡突然叫「Free WiFi Login」的網路。每一個都是攻擊者可能潛伏、冒充你目標 SSH 伺服器的位置。

如果你的 SSH 客戶端正確保存了主機金鑰,這些都不重要。攻擊者的金鑰對不上,連線直接失敗。如果你的客戶端沒有保存主機金鑰(或每次都接受伺服器送來的任何東西),那你就毫無防護——等於把憑證遞給任何在 22 連接埠上回應的人。

大多數行動客戶端怎麼做(差)

設計 TermAI 之前我測試過好幾款熱門的行動 SSH 應用。最常見的預設行為:

  • 首次連線提示、之後不再複查。第一次連線時跳出指紋對話框,你按接受,金鑰被記下,之後每次默默比對。這正是 OpenSSH 的行為——實作正確時沒問題。
  • 首次連線提示但 UI 含糊。同上,但對話框寫著類似「無法驗證伺服器」和兩個按鈕「接受」/「取消」。大多數使用者反射性地按接受。更糟的是,金鑰之後變了,界面給的還是同一個對話框,使用者照樣接受。金鑰變更應該看起來跟首次連線完全不同。
  • 完全不管。有些 App 就直接連線,從不顯示指紋,也從不在金鑰變化時警告。這是設計上就壞掉的。

第三類比我預想的常見得多,付費產品裡也有。

OpenSSH 是怎麼做的

OpenSSH 的 known_hosts 檔案是參考實作。行為如下:

# 首次連線
$ ssh prod-server
The authenticity of host 'prod-server' can't be established.
ED25519 key fingerprint is SHA256:abc...xyz.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'prod-server' to the list of known hosts.

# 之後,金鑰若發生變更
$ ssh prod-server
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Host key verification failed.

首次提示客氣,金鑰不匹配時立刻變得敵意明顯、直接拒絕連線。你必須手動刪掉舊條目才能繼續。這種不對稱——首次平靜、變更強硬——正是 TermAI 沿用的設計。

TermAI 是怎麼做的

我們把主機指紋存在裝置上一個 AES 加密的 Hive 資料庫裡。加密金鑰放在 iOS Keychain(或 Android Keystore 等價物)裡,所以即使有人把資料庫檔案從裝置裡拿走,沒系統金鑰串解鎖也讀不出來。

  1. 首次連線。伺服器出示主機金鑰,TermAI 算出 SHA256 指紋並顯示:SHA256:abc...xyz。可以點擊複製。接受按鈕是預設選項,取消按鈕也清楚可見。一旦接受,指紋就會被存起來。
  2. 後續連線。指紋與儲存值匹配→連線默默繼續。沒有提示、沒有警告、不打斷。
  3. 後續連線出現不匹配。硬停。紅色對話框。對話框解釋發生了什麼、同時顯示已存指紋與新指紋,並且沒有一鍵接受按鈕。要繼續,必須進入設定→Known Hosts明確刪除已存指紋,再重新連線、重新信任。這是刻意設計的兩步流程。

兩步流程是關鍵。如果一次點擊就能覆蓋金鑰不匹配警告,使用者在時間壓力下會一路點過去。這個打斷本身就是保護。

怎麼看出真的中間人攻擊

如果客戶端警告金鑰變了,要問的是:伺服器金鑰是因為正當原因真的換了,還是有什麼在攻擊這條連線?

正當原因:

  • 你給伺服器重灌了系統
  • 你主動輪換了主機金鑰
  • 你把 DNS 主機名指向了另一台伺服器
  • 伺服器在負載平衡器後面,這次連到不同的後端(應給所有後端配同一把金鑰)

可疑原因:

  • 你在一個不完全信任的新 Wi-Fi 上
  • 你剛遇到一個預期外的強制入口
  • 伺服器管理員沒告訴你要換金鑰
  • 在合法伺服器上對過之後,這指紋依然完全陌生

不確定時的正確做法:別連。打開伺服器的網頁控制台 / 雲服務商的序列埠控制台 / 同網段一台可信機器,對伺服器跑 ssh-keyscan,對比指紋。如果跟你客戶端看到的新金鑰一致,就沒事。不一致,那你在跟中間人通話。

合法的金鑰變更

如果你打算正當地輪換主機金鑰(最佳實踐:每兩年左右一次),有乾淨的做法讓客戶端不至於全慌:

  1. 產生新主機金鑰,但保留舊的可接受狀態
  2. 過渡期內讓 sshd 同時提供兩把金鑰
  3. 透過使用者信任的管道把新指紋分發出去(團隊密碼管理工具、簽名公告)
  4. 所有人都更新完成後,把舊金鑰移除

個人伺服器更簡單:跟用的人提前說「我週二輪換主機金鑰,新指紋是 XX」。打個招呼,就把一個警告對話框變成預期內的確認。

快速事實

  • TOFU = 首次使用即信任:與 OpenSSH 相同的主機金鑰模型
  • 按連線固定金鑰:金鑰變化時給出同時顯示新舊指紋的警告
  • 帶外核實:在伺服器上跑 ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub,對比後再接受
  • 防中間人:金鑰變化正是中間人攻擊的樣子,所以永不自動接受
  • 永不關閉校驗:StrictHostKeyChecking no 會拆掉對中間人唯一的防禦
試試 TermAI

免費上手,iOS 和 Android 都有。免費檔每天 5 次 AI,SSH/SFTP 不限量,內建 Tailscale。

CC
Chen Chen — Founder of TermAI

Writes about mobile DevOps, terminal UX, and the surprising depth of "boring" infrastructure.

Was this useful? ← Back to blog