使用 TOTP 登入 SSH
阿恆
以前我過討論 SSH 的安全設定 (這裏和這裏),建議大家使用同時使用密碼和公鑰來登入 SSH,強化系統的安全性。今天我介紹另一個用戶驗證機制給大家選擇,就是現今非常流行的一次性密碼,可以加入以上的機制成為三重驗證,或者自選其中兩者。
一次性密碼 (One Time Password, OTP) 意思是每次登入,你都要輸入一個不同的密碼,所以說是「一次性」,這個密碼即使給別人知道了也沒有用,因為它是有時限的 (通常 30 秒),過了時限便沒有用處。即使時限未過,別人也未必有你的另一重驗證方法 (例如常設的密碼或公鑰),所以系統仍然是安全的。
這個一次性密碼是怎樣產生的呢?方法是伺服器 (也稱為驗證者 verifier) 根據一個預先產生的密鑰 (encryption key) 和當前系統的時間,產生一個散列值 (hash value),你的隨身工具,例如手機或者桌面電腦,也有一個程式,它也知道這個密鑰,加上當前的時間,便可以得到與伺服器相同的散列值。用戶把這個散列值提供給伺服器,證明自己擁有與伺服器相同的密鑰。計算散列值時加入時間這個因素,令到散列值會隨時間而變化,這樣即使給人偷看到,過了一段時間便沒有用處,因為伺服器不會接受這個過時的散列值。此外,偷窺者也無法從這個散列值還原密鑰。
以上就是以時間為基礎的一次性密碼 (Time-based One-Time Password, TOTP) 的運作方法,我儘量說得簡單,希望大家容易明白,對於技術基礎比較好,希望更深入地了解的網友,可以閱讀 TOTP 的技術規格。
一次性密碼不一定以時間為基礎,例如可以用計數器 (counter) 為基礎,它其實比 TOTP 更早出現,由於與這篇文章的主題無關,我不打算在這裏詳細討論,有興趣的網友可以參考 HMAC-based One-Time Password 的技術規格。
言歸正傳,究竟怎樣在 SSH 加入 TOTP 呢?以下逐步講解。
在手機或者桌面電腦安裝 TOTP 驗證程式 最常見的應該是 Google Authenticator apps,不過我選擇了 Authy,這類 apps 有很多選擇,Ars Technica 有一篇很棒的文章討論怎樣選擇 TOTP 驗證程式,強烈推薦。
在伺服器安裝計算 TOTP 的工具 TOTP 的技術是公開的,有興趣、有能力的話你也可以自行開發,不過這麼有用的東西,早已有人做了,那就是 Google 的 google authenticator (以下簡稱 GA)。那是一個開放源碼的項目,有興趣的可以到 Github 看看。安裝 GA 的方法很簡單:
1
sudo apt install -y libpam-google-authenticator
建立一個 TOTP 密鑰 執行 GA,它便會產生一個密鑰。輸入:
1
google-authenticator
GA 問
Do you want authentication tokens to be time-based (y/n)
,答y
的話表示會使用 TOTP,答n
的話表示使用 HOTP。建議答y
。GA 跟着在屏幕顯示一個 QR code (用 ASCII art 模擬的),它包含了 TOTP 密鑰和一些伺服器的資料。
在手機開啟 Authy (或任何 TOTP authenticator),新增一個帳號 (account),掃描 GA 顯示的 QR code。
在 Authy 輸入帳號名稱,這是一個方便你辨別帳號的名稱,喜歡輸入甚麼便輸入甚麼,你可以順便設定帳號的圖示,然後按
Save
。Authy 這時會顯示新帳號的 TOTP,回到 GA,在
Enter code from app (-1 to skip):
輸入那個 TOTP。這一步是確認手機上產生的 TOTP 與伺服器上產生的 TOTP 一致,注意 TOTP 只有 30 秒時限,Authy 在時限過後會自動產生一個新的 TOTP,你必須輸入最新的 TOTP。你可以跳過這個確認手續,輸入-1
便可以了,但是我不建議你這樣做。GA 跟着會顯示 5 個「緊急登入碼」,倘若你的手機不在身邊或者遺失了,便可用這 5 個緊急碼登入 SSH。每一個緊急碼只可以用一次,全部用完便無法再登入 SSH。通常第一次使用緊急碼登入 SSH 後,會立即從新執行 GA,產生一個新的加密碼,並且在 Authy 建立一個新帳號。建議你把這 5 個緊急碼儲存在安全而容易找到的地方,例如一個密碼管理程式。
GA 跟着問哪裏儲存新的加密鑰:
Do you want me to update your "/username/.google_authenticator" file? (y/n)
,預設是用戶主目錄內的.google_authenticator
,除非有特別需要,例如你的主目錄已經加密,在成功登入前無法閱讀,否則不用更改,回答y
就可以了。下一步 GA 會問你是否容許同一個 TOTP 多次使用:
Do you want to disallow multiple uses of the same authentication token? This restricts you to one login about every 30s, but it increases your chances to notice or even prevent man-in-the-middle attacks (y/n)
,理論上在同一個 30 秒的時限內,只會有一個 TOTP,如果你在這段時間內多次或同時登入 SSH,便應該使用同一個 TOTP,但是容許同一個 TOTP 重複使用,萬一有人偷窺你的 TOTP,迅速 (在時限完結前) 用它來登入 SSH 怎麼辦?你可以告訴 GA 不接受重複使用 TOTP,這樣會做成一點兒不便,就是你每次登入 SSH,都要相隔最少 30 秒。這裏指的是成功的登入,如果輸入錯誤的 TOTP,你還是可以立即再輸入,不用等 30 秒。視乎你的需要,想提高安全性的,回答y
;性提高方便性的,回答n
。由於 TOTP 依靠當前的時間來產生,如果伺服器的時鐘與手機的時鐘不同步,雙方產生的 TOTP 便不相同,你便永遠無法登入 SSH。所以 GA 容許你輸入前一個 30 秒時段的 TOTP、當下的 TOTP 和下一個 30 秒時段的 TOTP,這樣只要手機和伺服器的時鐘相差不超過 30 秒,或者你看到手機上的 TOTP 後,小心謹慎慢慢地輸入,仍然可以成功登入。這個設定稱為 TOTP 的「窗口期」,預設是 3 (前一個時段,當下時段,後一個時段),你可以更改到最大 17 (前 8 個時段,當下時段,後 8 個時段,容許伺服器和手機的時鐘相差 4 分鐘)。看到
By default, a new token is generated every 30 seconds by the mobile app. In order to compensate for possible time-skew between the client and the server, we allow an extra token before and after the current time. This allows for a time skew of up to 30 seconds between authentication server and client. If you experience problems with poor time synchronization, you can increase the window from its default size of 3 permitted codes (one previous code, the current code, the next code) to 17 permitted codes (the 8 previous codes, the current code, and the 8 next codes). This will permit for a time skew of up to 4 minutes between client and server. Do you want to do so? (y/n)
,可以輸入y
使用預設「窗口期」3。來到最後一道問題,為了保障系統不受暴力破解,GA 可以設定每 30 秒只容許 3 次錯誤的 TOTP。連續 3 次輸入錯誤的 TOTP 後,便要等 30 秒才能嘗試登入。見到
If the computer that you are logging into isn't hardened against brute-force login attempts, you can enable rate-limiting for the authentication module. By default, this limits attackers to no more than 3 login attempts every 30s. Do you want to enable rate-limiting? (y/n) y
,為了較好的安全性,建議答y
。修改 SSH 的配置,我假設你將會使用公鑰驗證法和 TOTP,放棄使用密碼登入,在
/etc/ssh/sshd_config
加入或者修改以下 3 句:1 2 3
UsePAM yes ChallengeResponseAuthentication yes AuthenticationMethods publickey,keyboard-interactive
修改 PAM 配置檔案
/etc/pam.d/sshd
,加入:1
auth required pam_google_authenticator.so
並且把以下一句前面加上
#
,避免 PAM 模組提問用戶密碼:1
#@include common-auth
最後,重新啟動 SSH:
1
sudo systemctl restart sshd.service
就是這樣了。下次你登入 SSH,系統首先會進行公鑰驗證,通過的話便會透過 PAM 模組,調用 google-authenticator.so
動態函式,要求你輸入 TOTP,這時你查看手機上的 Authy,把 TOTP (一組 6 位的數字) 輸入,便成功登入 SSH。
在進行上述配置時,謹記以下幾點:
- 備份有關的配置檔案,包括 SSH (/etc/ssh/sshd_config) 和 PAM (/etc/pam.d/sshd)。
- 把緊急碼儲存在安全而容易找到的地方。
- 使用一個終端機進行配置,另一個終端機進行測試,在測試成功 (成功透過 TOTP 登入 SSH) 前,千萬不要登出進行配置的終端機。