使用 Java 解析和转换 OpenSSH 和 PuTTY 私钥格式

小夏 科技 更新 2024-01-28

两种常用的 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 客户端进行开发。

相似文章

    使用JAVA租房系统源码,网上租房更方便

    使用j a租房系统的源码 网上租房更方便。随着互联网的普及和技术的进步,网上租房已成为一种趋势。采用J a开发的租赁系统源码,为用户提供更便捷 更高效的租赁体验。本文将利用J A的优势,实现网租的便利性。 J A租赁系统源码优势。跨平台 J a 是一种跨平台语言,它开发了一个可以在多个操作系统和设备...

    如何使用按摩板清洁痤疮?

    按摩板是一种可以有效清洁面部和预防粉刺的工具,下面介绍如何使用按摩板清洁痘痘 清洁面部。在使用按摩板之前,首先需要用温水或清洁剂清洁脸部,以去除表面的污垢和油脂。然后,用唇轻轻擦干脸部。涂抹洁面乳或润唇膏。将适量的洁面乳或润唇膏均匀涂抹在脸上,这样在使用按摩板时更容易滑动,也有助于提高清洁效果。使用...

    使用空化射流销毁弹药的优点

    利用空化射流排空装药弹产生的废水经澄清后可循环进入空化射流射流去除工艺,经多层填料过滤器和活性炭过滤器过滤,清除后的废炸药经技术处理后可用于民用爆破,过滤器中的废活性炭可由焚烧炉处理,提高了废炸药的再利用率,避免了环境污染。国外常采用空化射流空心火箭发动机推进剂和空心不易熔化 组分 和索金 蜡制品 ...

    Java 并行编程 fork Join 框架解释

    image.PNG分而治之算法是一种解决问题的策略,它将一个复杂的问题分解成几个较小的 相似的子问题,递归地求解这些子问题,然后将这些子问题的解合并,得到原来问题的解。分而治之算法的步骤通常由三个主要部分组成 分 将原始问题分解为一系列子问题。这些子问题应该更小,更容易解决原始问题的版本。征服 递归...

    参考 java 中 void 的用法

    在 J A 编程语言中,void 是一个特殊关键字,用于指示方法没有返回值。此关键字在定义方法时使用,指示该方法不向调用方返回任何值。在方法主体中,可以调用其他方法并执行各种操作,但最终它们不会返回任何结果。下面是使用 void 关键字的示例 j a复制 在此示例中,我们定义了一个名为sayhell...