兩種常用的 SSH 金鑰格式及其轉換方式。
Secure Shell (SSH) 是一種建立在應用層和傳輸層之上的安全協議,由 IETF 的網路工作組開發。 SSH 是當今最可靠的協議,可為 telnet 會話和其他網路服務提供安全性。 SSH協議可以有效防止遠端管理過程中的資訊洩露。 除了使用者名稱和密碼的密碼認證方法以及金鑰認證方法外,SSH還可用於驗證客戶端和伺服器之間通訊的安全性。
在程式開發中,我們經常需要使用程式與伺服器或儲存裝置建立SSH連線來管理裝置(如重啟裝置、檢視硬體資訊等),目前J**a的開源SSH庫一般只支援OpenSSH格式的金鑰連線,但在日常使用中,大量客戶會使用Putty來建立SSH金鑰, 這樣程式就需要解析和轉換金鑰。本文介紹如何使用 Orion SSH2 通過金鑰 SSH 連線到伺服器,並在 J**a 程式中執行操作,並分析了兩種常用 SSH 金鑰的格式和解析原理。
在本文中,我們將使用 Orion SSH2 作為 SSH 客戶端,然後使用金鑰訪問 Linux 伺服器並執行 Linux 命令。
首先,我們需要在 Linux 伺服器上生成一對金鑰,將公鑰註冊到伺服器,然後將私鑰傳輸到本地計算機,然後使用 putty 載入私鑰並連線到 Linux 伺服器。 伺服器管理工具、儲存裝置、交換機等很多裝置也都提供了SSH連線功能,有時候我們需要根據使用者需求開發程式來管理這些裝置,這時我們就可以使用SSH客戶端庫來開發。
Orion SSH2 是乙個純 JSH-2 協議包,允許 JSH 程式通過 SSH 協議連線到伺服器,以執行遠端命令和檔案傳輸功能。 通過ORION SSH2,我們只需要傳入SSH伺服器的IP、埠和金鑰資訊即可建立SSH連線,CLI命令的具體程式碼如下:
string hostname = "192.168.1.2"; string username = "root";輸入金鑰所在的路徑 file keyfile = new file("c:\\temp\\private");輸入金鑰的加密密碼,不能設定為空字串 keyfilepass ="joespass";嘗試關閉 SSH 會話 SESSclose();關閉 SSH 連線連線close();catch (ioexception e)
以上 ** 顯示了連線到 Linux 系統並執行命令 uname -a &&&&date &&uptime &&&WHO 列印作業系統資訊、當前系統時間、系統執行時間以及當前登入使用者資訊的過程。 同樣,我們可以用這個**來連線伺服器的IMM(Integrated Management Module)來執行命令來檢視伺服器的網路配置和硬體資訊或重啟伺服器,或者我們可以連線到IBM Storwize V7000的控制台來管理V7000。
OpenSSH 在 RFC4716 中定義了公鑰和私鑰的格式,它簡單地由乙個初始建立者、乙個標頭、乙個主體和乙個最終建立者組成。 下面是乙個未加密的私鑰(在本文中,我們將使用未加密的 RSA 私鑰進行解析)。
---begin rsa private key---miicwgibaakbggabzjs7fo9heoxvzqnjhu9wtofbnx+rmgtrtra0pqg0ekori7mnskd3k8zgs3tgjveww48ydufvqndwgpdwztlhbgpydbqk1ki87arazrexaely7tq60+cpj8vgkvec3k1zh0/tkruhlp/6c8v3soqi56d1/zjqwerz8fvfqmy9agelaogaicsukvoxtc0bi0m8sycmi3fzsekkgbbq9uy5rnqwaxbdlihhhckptfx6n6vvflcpdragk1ue1azmnhajv82l6xm4s1m06jmq2uvvmpnjpecn978u9qpjke+iqdys1gs6ck3xqyfkm1zgrpckbqfl4fswh62pwuh5amxlzu71r60cqqc5ebbwygtn7bnoqgtv/gdzdwqwimbfrpbqfgziwlw**lb8kcj7trkduzg6yt8zcszdkgsjxkgkqs51f+uk/b0bakeahpgeazdmu9rpzzwx0mm6sxdmsrkgpfvelxkhqheakiaorhuasr3xn0dr4r4hpppwwvg4aqmjybi+ug7n5n1dv**bakapuhwbvw3frsa3ssfg6/n/jiftsuqd5jhi/ppat5bfzrdrxbeeb8ugonjmzsmlzrkn0jgdoi5ozjwbm15do60cqcpurmfjuq7hoclmycqvoskjwc9ujcxtjxoiab5lfjxfozwk624lf3a5w21gafwrcwq/mabjhhn3f99crxwgicucqqccdz8y99auopppd1t4z1wemc44nyl/m+ixgxieveauwv0udbbtyuqx+9gh2wimvyfl8uggtqci22n6xt/u+w8n---end rsa private key---
OpenSSH 支援 RSA 和 DSA 金鑰,本文將以 RSA 金鑰為例進行解析。 RSA金鑰的語法結構在文件RFC3447中定義,私鑰的語法結構如下:
rsaprivatekey ::= sequence
version 是 RSA 的版本號,本文中使用的金鑰是版本 0,如果金鑰使用多個素數(超過 2 個素數),則為 1。 模量是 RSA 的復合模量 n。 Publicexponent 是 RSA 的公共權力 e。 Privateexponent 是 rsa 的私有冪 d。 prime1 是 n 的質因數 p。 素數2 i 是 n 的質因數 q。 指數 1 等於 D mod (p 1)。 指數 2 等於 D mod (q 1)。 係數是 CRT 係數 q 1 mod p。 OtherprimeInfos 包含其他素數 r3 ,......挨次如。 如果 version 為 0,則應忽略;如果 version 為 1,則它應該至少包含乙個 otherprimeinfo 例項。 RSA公鑰的語法結構如下圖所示,可以看出公鑰所需的因子資訊包含在私鑰中。
rsapublickey ::= sequence
OpenSSH 的 RSA 金鑰的檔案正文是 DER 編碼的,JDK 提供了乙個 DerInputStream 來解析 DER 編碼的字串。 因此,openssh 金鑰的解析非常簡單,首先讀取金鑰,過濾掉開始和結束標誌,檔案頭(如果是加密金鑰,則需要根據檔案頭資訊確定解密方法,因為本文使用的是未加密的金鑰,去掉檔案頭),然後使用 der inputstream 解析金鑰, **如下:
string keyinfo = "";string line = null;刪除檔案開頭和結尾的注釋,同時 ((line = br..)readline())= null) } 金鑰資訊採用 base64 編碼加密,需要解密位元組 decodeKeyInfo = (new base64DeCoder())DecodeBuffer(KeyInfo);使用 derinputstream 讀取金鑰資訊,derinputstream dis = new derinputstream(decodekeyinfo);該鍵不包含 otherprimeinfos 資訊,因此只有 9 個 dervalue ders = disgetsequence(9);讀取 rsa 因子資訊 int version = ders[0]。getbiginteger().intvalue();biginteger modulus = ders[1].getbiginteger();biginteger publicexponent = ders[2].getbiginteger();biginteger privateexponent = ders[3].getbiginteger();biginteger primep = ders[4].getbiginteger();biginteger primeq = ders[5].getbiginteger();biginteger primeexponentp = ders[6].getbiginteger();biginteger primeexponentq = ders[7].getbiginteger();biginteger crtcoefficient = ders[8].getbiginteger();//generate public key and private keykeyfactory keyfactory = keyfactory.getinstance("rsa");rsapublickeyspec rsapublickeyspec =new rsapublickeyspec(modulus, publicexponent);publickey publickey = keyfactory.generatepublic(rsapublickeyspec);rsaprivatecrtkeyspec rsaprivatekeyspec =new rsaprivatecrtkeyspec(modulus,publicexponent,privateexponent,primep,primeq,primeexponentp,primeexponentq,crtcoefficient);privatekey privatekey = keyfactory.generateprivate(rsaprivatekeyspec);
SSH 金鑰的解析也可以在 Orion SSH2 的原始碼中看到,詳見 pemdecoder 類,以下是解析 RSA 私鑰的片段:
if (ps.pemtype == pem_rsa_private_key)
金鑰解析完成後,可以使用這些金鑰因子資訊與 SSH 伺服器進行驗證,具體的驗證流程請參考 OION SSH2 中的 AuthenticationManager 類。
Putty 沒有提供其金鑰語法的文件,但 Putty 是乙個開源專案,我們可以從其網站 Putty 的原始碼中了解它是如何構建金鑰的。
從 putty 原始碼中我們可以看到 putty 的金鑰結構如下:
putty-user-key-file-2:encryption:comment: public-lines:private-lines:private-mac:
第一行的金鑰演算法標記了金鑰的演算法,“ssh-rsa”表示使用了RSA演算法。 下行的加密表示金鑰的加密方式,目前僅支援“AES256-CBC”和“None”。 公鑰和私鑰後面的數字分別表示公鑰和私鑰的行數。 專用 MAC 是此金鑰資訊的 HMAC-SHA1 值。
實際的 ppk 金鑰如下:
putty-user-key-file-2: ssh-rsaencryption: nonecomment: imported-openssh-keypublic-lines: 4aaaab3nzac1yc2eaaaabjqaaaibgg847o36pr3qmb2ap4x7vvkzhwzv/ktbrubawnd6hthpdq4uzj0pa9yvgyln7ro1rmfupghvh1ajq8bqxcgbzyqyd8nw6pnsovo2qwgaxmqbjcu00otpgj4/lxpfratytwr9p05evb5t/+**fd0jqioendf2y6lnq2fbvrajmvq==private-lines: 8aaaagcheljfaf7xnaytjvemhjotxwuhcpbgq**vgoutufgf2wyyiyyqij7x1+p+lb35xdw6wictbntqmzjxwcvfni+stoettnoo5qtll1zqty6xgjfe/fpuky5hvoka2etyeunct8ugbsjncxqzwpg6hzebbfh+tqvrh+wpss87u9uetaaaaqqc5ebbwygtn7bnoqgtv/gdzdwqwimbfrpbqfgziwlw**lb8kcj7trkduzg6yt8zcszdkgsjxkgkqs51f+uk/b0baaaaqqce8z4bkmxt2ullnbfqwzqxcoaysqa9+8svcodad4a**o5gg5pkvfe3r2vhhigmk/bzubhqqangej66ds3k3uo9aaaaqqccdz8y99auopppd1t4z1wemc44nyl/m+ixgxieveauwv0udbbtyuqx+9gh2wimvyfl8uggtqci22n6xt/u+w8nprivate-mac: 4c039beb12ae005acb763225572cc4eeec767542
從 Putty 的原始碼可以看出,ppk 中的公鑰包含以下資訊:
string "ssh-rsa"mpint exponentmpint modulus
私鑰包含以下資訊:
mpint private_exponentmpint p (the larger of the two primes)mpint q (the smaller prime)mpint iqmp (the inverse of q modulo p)data padding (to reach a multiple of the cipher block size)
此因素資訊可以在上面解釋的 OpenSSH 私鑰格式中找到。
公鑰和私鑰資訊都遵循 RFC 4251 中定義的資料格式,這僅意味著每個元素都儲存為位元組流,前 4 個位元組是整數,數值是元素的位元組長度。 由於私鑰只儲存 IQPm(即 OpenSSH 稱之為係數),我們還需要計算 primep(即上面的 prime1)和 primeq(即上面的 prime2)。 具體分析如下:
public static void testppk() catch (exception e) 讀取前 4 個位元組獲取元素長度,然後讀取該元素的位元組資訊並將其轉換為 biginteger public static biginteger readint(datainputstream dis) 丟擲 ioexception 將鍵資訊解析為鍵值對 private static map parsekv(string file) 丟擲 ioexception }else s = s + line; kv.put(k, s); finally return kv; }
ORION SSH2 不支援 PPK 格式的 SSH 金鑰,但是通過以上方法,我們可以自己從 PPK 中讀取 RSA 的每個元素,這樣在 ORION SSH2 的 AuthenticationManager 中,我們可以將 PPK 格式金鑰的判斷和處理新增到 ORION SSH2 的 AuthenticationManager 中,這樣 ORION SSH2 就可以使用 PPK 連線到 SSH 伺服器了。
大多數 j**a ssh 客戶端庫只提供對 openssh 金鑰的支援,但在實踐中,大多數使用者使用 putty 來生成和管理 ssh 金鑰,使用上述方法可以讓我們編寫直接支援 ppk 金鑰的程式,而無需使用者在使用程式前轉換金鑰格式。
同樣,為了方便使用者,我們可以在程式中提供金鑰格式轉換功能。
要轉換這兩類金鑰,首先需要將它們解析成金鑰演算法對應的因子,解析方法在前兩節中已經介紹過了,所以我就不重複了,下面只說明如何根據RSA的因子生成OpenSSH格式或者PPK格式的金鑰。
OpenSSH 金鑰使用 DER 編碼來記錄資訊,而 J**A 提供了 deroutputstream 來寫入 der 資料,唯一需要注意的是,在構建了金鑰的位元組資訊後,還需要寫入 der 的序列標籤資訊,以表明資訊是有序的。 轉換後的**如下:
deroutputstream deros = new deroutputstream();deros.putinteger(version);deros.putinteger(modulus);deros.putinteger(publicexponent);deros.putinteger(privateexponent);deros.putinteger(primep);deros.putinteger(primeq);deros.putinteger(primeexponentp);deros.putinteger(primeexponentq);deros.putinteger(crtcoefficient);deros.flush();byte rsainfo = deros.tobytearray();deros.close();deros = new deroutputstream();在寫入資訊時,需要編寫序列標記write(dervalue.tag_sequence, rsainfo);deros.flush();寫入需要使用 base64 編碼進行加密:string keyinfo = (new base64encoder())encode(deros.tobytearray())
在PPK金鑰格式解析中對PPK的資訊格式進行了分析,在得到RSA的計算因子後寫入資訊時,計算每個因子的位元組長度並寫入長度資訊,然後寫入因子的位元組資訊,以公鑰資訊為例
bytearrayoutputstream bos = new bytearrayoutputstream();寫出元素bos的長度資訊write(convertinttobytearray(keyalgo.length()) 寫入位元組資訊 bos。元素的write(keyalgo.getbytes())bos.write(convertinttobytearray(publicexponent.tobytearray().length));bos.write(publicexponent.tobytearray())bos.write(convertinttobytearray(modulus.tobytearray().length));bos.write(modulus.tobytearray())bos.flush();string keyinfo = (new base64encoder())encode(bos.tobytearray())bos.close();public static byte convertinttobytearray(int value)
SSH 是一種使用非常廣泛的協議,許多伺服器管理工具(如 IMM、CMM)、儲存裝置(如 IBM Storwize V7000)和交換機都提供了 SSH 伺服器,供管理員使用 SSH 客戶端(如 putty)登入,並使用命令列檢視裝置資訊和遠端控制裝置,因此許多虛擬機器管理程式也使用 SSH 客戶端來管理這些裝置。
本文以開源的 J**A SSH 客戶端(Orion SSH2)為例,講解 SSH 金鑰的結構以及 SSH 客戶端對金鑰的解析過程,希望能幫助大家了解 SSH 金鑰,並使用 SSH 客戶端進行開發。