[Security] 你該知道所有關於 SSH 的那些事

是什麼、怎麼用、要小心什麼

網路安全系列文第二篇!今天要介紹一個很重要的工具:SSH (Secure Shell)。對於開發者而言,SSH 這個詞應該很不陌生。當我們想要進入遠端伺服器,通常會下 ssh [email protected]_host,連接成功就可以直接讓遠端伺服器執行我們在本機上下的指令。有沒有想過 SSH 是什麼?它是如何讓我們可以這樣看似隨隨便便就進入一個應該是被嚴格保護的伺服器?它怎麼確保你可以登入,而壞人小王不行?怎麼確保連線的時候小王沒有從中竊聽攔截竄改資料?話不多說,馬上捲起袖子來看 SSH 到底是如何運作!

SSH 是一個連線加密機制

SSH 的先行者們(FTP, Telnet, rlogin, rsh 等)誕生於一個純真的年代,當時網路的使用還多在軍事與學術界,壞人還沒有開始湧入,因此安全性不是優先考量,就連敏感資料如密碼,也都是大大方方地以明文(plaintext)傳送。

SSH 的出現就是為了解決這樣子的情況。安全是 SSH 的賣點。SSH 它讓我們在與遠端電腦(遠端伺服器)連線時,能夠先將訊息加密過後再傳送,並且確保只有「被認可的人」才能夠解密訊息。因此,就算是在一個不安全的網絡裡面,SSH 都能夠確保連線不被竊聽。

想當然耳,現在已經很少人使用上述的連線方式了,基本上都被 SSH 取代。尤其像是 OpenSSH 這個開源專案的出現,讓我們還真的找不到什麼理由不用 SSH。

SSH 的 Client-Server Model

SSH 使用 client-server 模型,也就是說,要建立一個 SSH 連線,遠端的機器必須跑一個 SSH daemon,而本地機器則要有一個 SSH client 程序。SSH daemon 會預設聽從 TCP port 22 進來的連線,並在認證後提供相對應的環境給使用者。SSH client 則負責使用 SSH protocol 來傳送認證訊息與連線細節給遠端機器。

SSH 最常見的使用情況就是開頭提到的遠端連線,不過它同時也支援 tunneling 或是也可以使用 SFTP (SSH File Transfer)SCP (Secure Copy) 來進行檔案傳輸。

許多作業系統,包含 macOS, Linux, OpenBSD, FreeBSD 都支援 SSH。

SSH 與 Public Key Crytography (PK crypto)

SSH 使用 public-key cryptography 來為資料流加密。

在一個 public-key 結構中,每一個使用者都有兩把鑰匙:公鑰(public key)私鑰(secret key)。私鑰拿來做電子簽名與解密(任何你收到的加密文件),公鑰用來讓別人確認你的簽名的正確性(證明人家收到的,有你的簽名的東西真的是你傳的),還有讓別人將東西加密成只有你自己能(使用私鑰)解密的檔案。

假設今天小明想要使用 SSH 傳一個訊息給小美,他們已經互相擁有對方的公鑰了,並且擁有各自的私鑰,此時會有五個步驟:

  1. 小明用自己的私鑰將訊息簽名。
  2. 小明用小美的公鑰將訊息加密。(此時,被加密的訊息連小明也無法還原—只有小美的私鑰可以!)
  3. 小明將訊息傳給小美。
  4. 小美將訊息用自己的私鑰解密。
  5. 小美用小明的公鑰來確認這個訊息是用小明的私鑰簽名的。

是不是很聰明!

雖然比起分組加密/區塊加密(Block Cipher)(一種對稱金鑰演算法,使用同一組鑰匙來做加密與解密的動作),PK crypto 聽起來很冗,並且比起其他演算法需要更多時間與 CPU,但卻是必要的麻煩,因為它讓我們可以傳送加密訊息而不需要先交換任何秘密資訊。

仍然需要注意的是

如何驗證未知的公鑰。上面的例子聽起來萬無一失,但是假設今天小明與小美還沒有交換公鑰,他們就必須要先連線傳送公鑰給對方。如果壞人小王在小明要把公鑰傳給小美時調包,換成小王自己的公鑰,這時與小美建立「安全連線」的人,就會是小王而不是小明了 😱 SSH 能夠提供的保護只在確保公鑰的擁有者同時擁有相對應的私鑰,但是不能保證擁有這個公私鑰組合的人是不是壞人。這就是著名的「在中間的人攻擊」(man-in-the-middle-attack)。這個弱點可以用認證系統(authentication),像是 PKI (Public Key Infrastructure) 來抵擋(系列下篇文章應該會好好講這個)。

加密演算法 RSA vs DSA

當我們在使用 SSH 時,很常會看到這兩個名詞出現。RSA 與 DSA 都是公鑰加密演算法(public key encryption algorithms),RSA ((Rivest–Shamir–Adleman) 是最早出現的加密演算法之一,之前受到專利保護,目前已經過效期了所以所有人都可以使用。所有的 SSH client 都支援 RSA,不過因為它很久了,所以建議使用 4096 bits 的鑰匙長度來加密(預設是 2048 bits),比較安全。DSA (Digital Signature Algorithm) 是另一個常被使用的演算法,不過由於該演算法的特性(使用的機制與鑰匙長度規定只能是 1024 bits),目前建議(也是預設)的演算法還是 RSA。

除了這兩者之外,還有像是 ECDSA, ED22519 等比較新的演算法也都有支援了。

OpenSSH

SSH 本身是一個協定(protocol),而 OpenSSH 是一個 SSH 協定的開源實作,也可以說是遵守 SSH 的一組工具。sshd 是 OpenSSH 的 server component,當出現一個連線請求,sshd 會根據 client 來建立對應的連線。比如說,如果今天是一個 ssh client,那 OpenSSH server 就會建立一個遠端操控環境,如果今天是一個 scp (secure copy) client,那 OpenSSH server 就會建立一個檔案的 secure copy。

SSH vs SSL

SSH 與 SSL 名字實在是太像了,而且做的事情也挺像,都是為了確保兩端連線安全而產生,但他們其實還是不一樣的概念,這邊就來稍微介紹一下 SSL。

先澄清一下,SSL (Secure Sockets Layer),實際上已經被 TLS (Transport Layer Security) 取代了(TLS 1.0 有時也被視為 SSL 3.1)。維基百科上甚至已經直接將兩者合併在 TLS 的條目下了。為何 SSL 不再繼續被使用了呢?因為它是很早就出現的一套網路連線防護機制,在駭客們努力不懈的堅持之下,SSL 已經差不多被破解完了,所以基本上非常透氣,防禦力趨近於零。不過慣性使然,許多人仍然使用 SSL 來稱呼這套機制。

SSL/TLS 其實大家都不陌生,HTTPS (HyperText Transfer Protocol Secure) (還有在網址前面那個 🔒 的圖案)是建立網站與訪客之間的安全連線的協定,取代了 HTTP 的不安全連線。

順帶一提,如果你還沒,請養成好習慣在進入任何網站時先確保網址的開頭是 https 而不是 http。

SSL/TLS 是 HTTPS 的幕後功臣。它們讓瀏覽器(客戶端)連上網站(伺服器端)的時候,先進行一個握手的儀式,並且使用 CA (Certificate Authority) 來進行身分確認。

有 CA 的參與也是 SSL/TLS 與 SSH 最大的差異。另外一個差異是 SSL/TLS 是被使用在資料傳輸,而 SSH 是用來執行指令(通常就是用來登入遠端電腦)。

說到 TLS,TLS 1.3 即將出世了。目前看到除了安全性升級之外,也將原本 asymmetric encryption 常常被詬病的低效率問題開刀,將客戶端與與伺服器端建立安全連線之前的身份確認程序簡化到一個來回(握一次手就好了),大大提升速度。

所以說到底怎麼用 SSH?用 GitHub 當例子

對於 SSH 實際的指令,Digital Ocean 有一篇詳細的教學,想要好好暸解 SSH 指令們的人可以參考。

以下就拿 GitHub 來當例子,有個實際的東西比較好將抽象概念印在腦海裡。GitHub 大概是每個開發者都會需要打交道的平台吧。當我們使用 git push 將在自己電腦中的 commit 們推到 GitHub 上時,其實背後就是使用了 SSH 連線(還記得當初設置帳號有經過一個很麻煩的 SSH 設定步驟嗎)。但總而言之,就是因為 SSH,我們可以不用每次都輸入密碼就連線到遠端的 repository。現在知道為什麼了吧?GitHub 官方教學在這。以下是幾個重要的步驟:

  1. 產生一組 public & private keys

    // 白話文:產生一組 ssh keys,使用 RSA 演算法加密,鑰匙長度 4096 bits,使用 [email protected] 作為這組 keys 的標籤。
    $ ssh-keygen -t rsa -b 4096 -C "[email protected]"
    > Generating public/private rsa key pair.
    
    // 接下來會問你鑰匙存放的位置還有 pass phrase,可以都直接用預設即可。
    > file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]
    > Enter passphrase (empty for no passphrase): [Type a passphrase]
    > Enter same passphrase again: [Type passphrase again]
    
  2. 把產生的 public & private keys 加入 ssh-agent (一個作業系統內建幫忙管理 ssh keys 的背景程式,讓使用者只需要輸入一次密碼往後就可以用密鑰自動登入)

    • 先將 ssh-agent 在背景啟動。會回傳該程式的 PID (process ID)。
    $ eval "$(ssh-agent -s)"
    > Agent pid 59566
    
    • 如果使用 macOS Sierra 10.12.2 之後的版本,要去修改 ~/.ssh/config,讓系統可以自動把鑰匙加入 ssh-agent 還有儲存 passphrase。
    Host *
      AddKeysToAgent yes
      UseKeychain yes
      IdentityFile ~/.ssh/id_rsa
    
    • 將 SSH private key 存進 ssh-agent 裡面。
    $ ssh-add -K ~/.ssh/id_rsa
    
  3. 將 public key 放到 Github 的 server 上。步驟在這請直接參考

小試身手:為什麽我們會需要把 public key 放到 GitHub server 上面呢?如果你有從上面好好看下來,應該會覺得很簡單。如果答不出來,那...那只好繼續看下去。

自問自答:因為在我們的例子中,GitHub 就是我們(clinet)想要連到的 remote server。要使用 SSH 連線,首先就是要讓那個伺服器知道你是一個合法的使用者。如果沒有將 public key 放在 remote server 上,那麼就算我們將加密的訊息傳過去了,這個伺服器還是不會知道要如何解密訊息。

如此一來,每當我們要送訊息(指令)給 GitHub (remote server) 時,我們就可以

  1. 用我們的私鑰加密指令。
  2. 用 Github 的公鑰加密指令。
  3. 傳送指令到 GitHub server。
  4. GitHub server 用它自己的私鑰解密。
  5. GitHub server 用我們的公鑰來驗證我們的身份。

這一切都是我們的 ssh client 幫我們自動完成,所以對我們而言我們可以直接使用。

就這樣

暸解 SSH,就會發現很多事情豁然開朗(程式方面啦),並且終於可以很帥的說出「我 ssh 進入 server 幫你喬一下」然後完全知道自己在說什麼了!

Reference

  • https://en.wikipedia.org/wiki/Secure_Shell
  • https://www.ssl.com/article/ssl-tls-handshake-overview/
  • https://www.ssl.com/article/tls-1-3-is-here-to-stay/
  • https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys#generating-and-working-with-ssh-keys

  • Find me at