OpenSSH 的 101 种用途:第二部分,共二部分

作者:Mick Bauer

大多数使用 SSH 的人从未使用过它最简单的两个功能:加密远程 shell 和加密文件传输(这也是上个月本专栏的全部内容)。这没关系;学习你不需要的功能毫无意义。但是,你们许多积极主动的读者无疑将从 SSH 的至少其他 99 种用途中受益。因此,让我们深入了解 SSH 真正酷的功能,特别是 OpenSSH 的功能。

注意:在本文中,我将使用“SSH”泛指安全 Shell,即“安全 Shell 系统”。特定的安全 Shell 进程将以等宽字体和小写字母显示,例如,ssh, sshd, 等等。这与我的其他文章一致:如果它看起来像你在控制台提示符下键入的内容,那么它很可能就是。

公钥加密

对公钥加密(或“PK 加密”)的完整描述根本无法在一篇关于 OpenSSH 的文章中容纳。如果你完全不熟悉 PK 加密,我强烈推荐 RSA 加密常见问题解答(可在 http://www.rsasecurity/rsalabs/faq/ 找到)或者,更好的是,Bruce Schneier 的优秀著作 应用密码学

就本文而言,只需说在公钥方案中,每个用户都有一对密钥就足够了。你的私钥用于数字签名事物,以及解密发送给你的事物。你的公钥被你的通信对象用于验证据称由你签名的事物,以及加密他们只想让你能够解密的数据。

The 101 Uses of OpenSSH: Part II of II

图 1. 公钥加密

在图 1 的底部,我们看到两个用户的密钥对如何用于签名、加密、解密和验证从一个用户发送到另一个用户的消息。请注意,Bob 和 Alice 拥有彼此公钥的副本,但每个人都对自己的私钥保密。

正如我们所见,消息的旅程包括四个不同的密钥操作:(1)Bob 使用他的私钥对消息进行签名;(2)Bob 使用 Alice 的公钥对其进行加密(注意:除了 Bob 可能保留了原始消息的副本之外,他不能解密此消息——只有 Alice 可以);(3)Alice 接收消息并使用她的私钥对其进行解密;以及(4)Alice 使用 Bob 的公钥验证它是使用他的私钥签名的。

与诸如 blowfish 和 IDEA 之类的分组密码相比,在分组密码中,相同的密钥既用于加密又用于解密,这似乎很复杂。与分组密码不同,对于分组密码而言,安全的密钥交换确保双方都获得密钥,而不会将密钥暴露于窃听或其他攻击,PK 加密更易于安全使用。

这是因为在 PK 方案中,双方可以相互发送加密消息,而无需首先交换任何秘密数据。有一个注意事项:公钥算法比其他类别的密码算法(如分组密码和流密码(例如,分别为 3DES 和 RC4))慢且 CPU 密集型更高。然而,碰巧的是,PK 加密可用于安全地生成可在其他算法中使用的密钥。

因此,在实践中,PK 加密通常用于身份验证(“你真的是你吗?”)和密钥协商(“我们将使用哪些 3DES 密钥来加密此会话的其余部分?”),但很少用于整个会话(数据流)或文件的批量加密。SSL 就是这种情况,SSH 也是如此。

高级 SSH 理论:SSH 如何使用 PK 加密

密钥协商是任何 SSH 事务中首先发生的事情之一,也是启用相对较弱的身份验证形式(用户名/密码组合)的原因。Diffie-Hellman 密钥交换协议的工作原理既超出了本文的范围,又很复杂(有关更多信息,请参阅名为“draft-ietf-secsh-transport-07.txt”的 Internet 草案,可在 http://www.ietf.org/ 找到)。你只需要知道,这个大素数运算的结果是一个会话密钥,双方都知道,但实际上并没有遍历尚未加密的连接。

此会话密钥用于通过双方主机商定的“分组密码”(透明地,但基于每个 SSH 进程的编译和配置方式)来加密所有后续数据包的数据字段。通常使用以下密码之一:三重 DES (3DES)、blowfish 或 IDEA。只有在会话加密开始后才能进行身份验证。

但在我们深入研究 RSA/DSA 身份验证之前,让我们先暂停片刻,考虑一下密钥协商如何能够透明化,因为它使用了 PK 加密,并且私钥通常受密码保护。SSH 使用两种不同的密钥对:主机密钥用户密钥

主机密钥是一种特殊的密钥对,它没有与之关联的密码。由于它可以无需任何人先输入密码即可使用,因此 SSH 可以协商密钥并完全透明地为用户设置加密会话。SSH 安装过程的一部分是生成主机密钥(对)。在设置时生成的主机密钥可以被该主机无限期地使用,除非 root 权限被泄露。并且由于主机密钥标识主机,而不是单个用户,因此每个主机只需要一个主机密钥。请注意,运行 SSH 的所有计算机都使用主机密钥,无论它们只运行 SSH 客户端 (ssh)、SSH 守护进程 (sshd) 还是两者都运行。

用户密钥是与单个用户关联的密钥,用于验证该用户对其发起连接的主机的身份。大多数用户密钥必须先用正确的密码解锁才能使用。

用户密钥提供了一种比用户名/密码身份验证更安全的身份验证机制(即使所有身份验证都通过加密会话进行)。因此,SSH 默认情况下始终在回退到用户名/密码之前尝试 PK 身份验证。

当你调用 SSH 时,SSH 会检查你的 $HOME/.ssh 目录,看看你是否有一个私钥(名为“id_dsa”)。如果你有,SSH 会提示你输入密钥的密码,然后使用私钥创建一个签名,该签名将与你的公钥副本一起发送到远程服务器。

服务器将检查公钥是否是允许的密钥(即,属于合法用户,因此存在于适用的 $HOME/.ssh/authorized_keys2 文件中)。如果密钥是允许的并且与服务器先前存储的副本相同,则服务器将使用它来验证签名是否是使用此密钥的相应私钥创建的。如果成功,服务器将允许会话继续进行,可能在还执行用户名/密码身份验证之后。

(注意:以上两段是指 SSH 协议 v.2 中使用的 DSA 身份验证;RSA 身份验证稍微复杂一些,但除了使用不同的文件名外,从用户的角度来看,功能上是相同的。)

PK 身份验证比用户名/密码更安全,因为数字签名无法被逆向工程或以其他方式操纵以推导出生成它的私钥;公钥也不能。通过仅在网络上发送数字签名和公钥,我们确保即使会话密钥以某种方式被破解,窃听者仍然无法获得足够的信息来非法登录。

设置和使用 RSA 和 DSA 身份验证

好的。你已准备好通过创建用户密钥对来进入 ssh-geekdom 的下一个级别。以下是你需要做的。

首先,在你的客户端系统(你希望用作远程控制台的机器)上,你需要运行 ssh-keygen。你有一些选择:除其他外,你可以指定 RSA 或 DSA 密钥;密钥长度;任意“注释”字段;要写入的密钥文件的名称;以及私钥将使用密码加密的密码(如果有)。

既然 RSA 的专利已过期,选择算法在某种程度上是任意的。另一方面,提交给 IETF 考虑作为互联网标准的 SSH 协议 v.2 使用 DSA 密钥。如果你两种方式都不在意,我建议使用 DSA。但如果出于任何原因你想要 RSA,那就用吧。-d 标志将 DSA 设置为算法,否则 RSA 是默认值。

密钥长度是一个更重要的参数。感谢 Adi Shamir 的“Twinkle”论文(该论文描述了一种理论上但可信的计算机,能够暴力破解 512 位或更低的 RSA/DSA 密钥),我强烈建议你创建 1024 位密钥;768 位密钥也可以,但使用起来并不比 1024 位密钥明显更快。但是,2048 位密钥绝对是多余的:它并没有明显更安全,并且会明显减慢速度。默认密钥长度为 1024,但你可以使用 -b 标志后跟一个数字来指定不同的长度。

“注释”字段不被任何 ssh 进程使用:它严格来说是为了你自己的方便。我通常将其设置为本地系统上的我的电子邮件地址。这样,如果我在其他系统上的 authorized_keys 文件中遇到密钥,我就知道它来自哪里。要指定注释,请使用 -C 标志。

密码和文件名可以,但不必在命令行中提供(分别使用 -N-f)。如果缺少,系统会提示你输入。

列表 1. 生成 DSA 用户密钥对

在列表 1 中,我正在创建一个密钥长度为 1024 位且注释字符串为 mbauer@sprecher.enrgi.com 的 DSA 密钥对。我让 ssh-keygen 提示我输入要将密钥保存在其中的文件。这将是私钥的名称,公钥将是此名称,并附加“.pub”。

在本示例中,我接受了 id_dsa 的默认文件名(因此为 id_dsa.pub)。我还让 ssh-keygen 提示我输入密码。星号字符串(“******************”)在您输入密码时实际上不会出现——我将它们插入到示例中,以表明我输入了一个长密码,该密码不会在屏幕上回显。

顺便说一句,密码是“全有或全无”的主张:你的密码应该为空(如果你打算将新密钥用作主机密钥或用于使用 SSH 的脚本),或者应该是一个长字符串,其中包含大小写字母、数字和标点符号的某种组合。这不像听起来那么难;例如,歌曲中的一行带有故意的但不可预测的拼写错误,可能很容易记住,但难以猜测。密码越随机,它就越强大。

这就是客户端必须完成的全部工作。在你要从此主机访问的每个远程机器上,所有需要做的就是将新的公钥添加到 $HOME/.ssh/authorized_keys2(其中“$HOME”是你的主目录的路径)。authorized_keys2 是公钥列表(每行一个非常长的行),用户的主目录 authorized_keys2 所在的目录中的用户可以使用这些公钥进行登录。

要将你的公钥添加到你拥有帐户的远程主机,只需将包含你的公钥的文件(在上面的示例中为 id_dsa.pub)传输到远程主机,并将其连接到你的 authorized_keys2 文件。你如何将文件传输到那里并不重要。请记住,它是你的公钥,因此如果它在途中被窃听者复制,则无需担心。但是,如果你想对此感到偏执,只需执行 scp ./id_dsa.pub remotehostname:/your/homedir——有关 scp 说明,请参阅上个月的专栏。然后将其添加到 authorized_keys2,登录到远程主机并输入

cat id_dsa.pub >> .ssh/authorized_keys2
  (assuming you're in your home directory)

就这样!现在,每当你使用 SSH 登录到该远程主机时,会话将看起来像你在列表 2 中看到的那样。

列表 2. 使用 DSA 密钥登录

请注意,当我在列表中调用 ssh 时,我使用了 -2 标志:这指示 SSH 仅尝试 SSH 协议 v.2。默认情况下使用协议 v.1,但 v.1 仅支持 RSA 密钥,而我们刚刚复制了一个 DSA 密钥。另请注意,密钥由其本地路径/文件名引用:这提醒我们,当我们使用 RSA 或 DSA 身份验证时,我们输入的密码仅在本地用于“解锁”我们本地存储的私钥,并且以任何形式通过网络发送。

关于上面的简单示例,我应该最后提一件事。它对远程服务器做了两个假设:(1)我拥有与本地相同的用户名,以及(2)远程服务器识别 SSH 协议 v.2。如果第一个假设不成立,我需要使用 -l 标志来指定我在远程主机上的用户名。(或者,我可以跳过 -l,而是使用 scp 样式的 username@hostname 语法,例如,mick@zippy.pinheads.com。)

如果远程 sshd 守护进程不支持协议 v.2,我将不得不再次尝试,不带 -2 标志,并让 SSH 回退到用户名/密码身份验证,除非我有一个 RSA 密钥对,其公钥已在远程机器上注册。

要使用 RSA 密钥完成所有这些操作,我们遵循相同的步骤,但使用不同的文件名

  1. 例如,使用 ssh-keygen 创建 RSA 用户密钥对

ssh-keygen -b 1024 -C mbauer@homebox.pinheads.com
  1. 在你要连接的每个远程主机上,将你的公钥复制到你的 $HOME/.ssh 目录中的 authorized_keys 文件中的单独一行。(RSA 密钥的默认文件名为 identity 和 identity.pub。)

同样,如果你在不带 -2 标志的情况下运行 ssh,它将默认尝试 RSA 身份验证。

如果你忘记了 RSA 或 DSA 密钥的密码会发生什么?你将如何返回远程机器以更改现在不可用的密钥的 authorized_keys 文件?不用担心:如果你尝试 RSA 或 DSA 身份验证并且由于任何原因失败,SSH 将恢复为用户名/密码身份验证,并提示你输入远程系统上的密码。

如果你希望禁用此“回退”机制,并保持仅 RSA/DSA 登录的严格策略,请在运行 sshd 的每个远程主机上的 sshd_config 中将参数 PasswordAuthentication 更改为“no”。只要我们谈论的是等式的服务器端,请注意,默认情况下,当 ssh 客户端进程请求时,sshd 允许 RSA 和 DSA 身份验证。显式允许或禁止这些的 sshd_config 参数分别是 RSAAuthenticationDSAAthentication

使用 ssh-agent 简化强身份验证

建立一个或多个用户密钥比用户名/密码身份验证提高了身份验证安全性,并利用了 SSH 的更多功能。这也是在 shell 脚本中使用 SSH 的第一步。自动化我们使用 PK 加密完成的事情只有一个小障碍:即使基于 PK 加密的身份验证是透明的,初步密钥授权也不是。我们如何安全地跳过或简化该过程?

有几种方法。一种是创建没有密码的密钥,在这种情况下,每当使用密钥时都不会提示输入密码。(我们稍后将讨论无密码密钥。)另一种方法是使用 ssh-agent

ssh-agent 本质上是 RAM 中的私钥缓存,它允许你在仅输入一次密码后重复使用你的私钥。当你启动 ssh-agent 然后使用 ssh-add 将密钥加载到其中时,系统会提示你输入密钥的密码,之后“解锁”的私钥将保存在内存中,以便所有后续调用 sshscp 都将能够使用缓存的、解锁的密钥,而无需重新提示输入密码。

这听起来可能不安全,但事实并非如此。首先,只有 ssh-agent 进程的所有者才能使用加载到其中的密钥。例如,如果“root”和“bubba”都已登录,并且每个人都启动了自己的 ssh-agent 进程并将各自的私钥加载到其中,则他们无法访问彼此缓存的密钥;bubba 没有使用 root 凭据运行 scp 或 ssh 进程的危险。

其次,ssh-agent 仅侦听本地 sshscp 进程;无法从网络直接访问它。换句话说,它是一个本地服务,而不是网络服务本身。因此,不存在外部潜在入侵者劫持或以其他方式破坏远程 ssh-agent 进程的危险。

使用 ssh-agent 非常简单:只需输入 ssh-agent 并执行它打印到屏幕上的命令。最后一点可能听起来令人困惑,而且肯定不是直观的:在转到后台之前,ssh-agent 打印一系列简短的环境变量声明,这些声明适用于你正在使用的任何 shell,必须在添加任何密钥之前进行声明。只需使用鼠标选择这些命令,然后右键单击以将其粘贴到命令提示符处以执行它们(请参阅列表 3)。

列表 3. 调用 ssh-agent

在列表 3 中,我已经完成了三分之一:我已经启动了一个 ssh-agent 进程,并且 ssh-agent 已经打印出我需要使用 BASH 语法声明的变量。

我现在需要做的就是选择上面第一行之后和最后一行之前的所有内容(一旦我松开鼠标左键,此文本将被复制),然后在最后一行上的光标上右键单击(这将把先前选择的文本粘贴到该位置)。我可能需要按 Enter 键才能执行最后一个 echo,但无论如何,该 echo 实际上不是必需的。

请注意,上述剪切和粘贴在任何 xterm 中都有效,但要在 tty(文本)控制台中工作,需要运行 gpm。如果一切都失败了,你始终可以手动键入声明。

一旦 ssh-agent 正在运行,并且 SSH_AUTH_SOCK 和 SSH_AGENT_PID 已声明并导出,就该加载你的私钥了。只需键入 ssh-add,后跟一个空格和你希望加载的私钥的名称(带完整路径)。如果你不指定文件,它将自动尝试加载 $HOME/.ssh/identity。由于这是 RSA 用户私钥的默认名称,如果你的私钥名称是其他名称,或者你希望加载 DSA 密钥,你将需要指定其名称,包括其完整路径,例如,ssh-add /home/mbauer/.ssh/id_dsa

你可以根据需要多次使用 ssh-add(以加载尽可能多的密钥)。如果你同时拥有 RSA 和 DSA 密钥对,并且访问运行不同 SSH 版本的不同远程主机(即,一些仅支持 RSA 密钥,另一些接受 DSA 密钥),这将非常有用。

用于最大程度可脚本化的无密码密钥

如果你从登录会话运行脚本,或者需要在单个会话中重复运行 ssh 和/或 scpssh-agent 非常有用。但是 cron 作业呢?显然,cron 无法执行用户名/密码输入 PK 身份验证的密码。

这是使用无密码密钥对的地方。只需按照上述说明运行 ssh-keygen,但在提示输入密码时按 Enter 键即可。你可能还需要输入“identity”或“id_dsa”以外的文件名,除非密钥对将成为用于运行自动化任务的某种特殊帐户的默认用户密钥。

要在 sshscp 会话中指定要使用的特定密钥,请使用 -i。例如,如果我在复制日志文件的 cron 作业中使用 scp,我的 scp 行可能如下所示

scp -i /etc/script_dsa_id /var/log/messages.*
   scriptboy@archive.g33kz.org

当脚本运行时,此行将在不需要密码的情况下运行:如果密码设置为 <Enter>,则 SSH 会足够智能,不会费心提示用户。

但请记住,在远程主机端,我需要确保 /etc/script_dsa_id.pub 中的密钥已添加到远程主机上的相应 authorized_keys2 文件中,例如,/home/scriptboy/.ssh/authorized_keys2

注意:始终保护所有私钥!如有疑问,chmod go-rwx private_key_filename

使用 SSH 执行远程命令

现在是时候从所有这些 PK 巫毒中退后一步,讨论 SSH 的一个简单功能,该功能对于脚本编写尤为重要:远程命令。到目前为止,我们一直在严格地将命令 ssh 用于远程 shell 会话。但是,这仅仅是其默认行为;如果我们使用命令行作为其最后一个参数调用 ssh,SSH 将执行该命令行,而不是远程主机上的 shell。

例如,假设我想快速查看远程系统的日志,如列表 4 所示。

列表 4. 在远程主机上运行 cat

如列表 4 所示,主机“zippy”会将 /var/log/messages 文件的内容发回我的本地控制台。(请注意,输出已通过管道传输到本地 more 进程。)

这里需要注意两点。(1)运行需要后续用户交互的远程命令很棘手,应避免——除了 shell 之外,ssh 在“触发”不需要用户输入的进程时效果最佳。此外,(2)所有身份验证规则仍然适用:如果你通常会被提示输入密码或密码,你仍然会被提示。因此,如果在 cron 作业或其他非交互式上下文中使用 SSH,请确保你使用的是无密码密钥,或者你正在使用的密钥首先已加载到 ssh-agent 中。

在我们离开脚本中的 SSH 主题之前,如果我不提及“rhosts”和“shosts”身份验证,我将感到失职。这些机制通过这些机制,可以自动授予从以下任何文件中指定的任何主机连接的用户访问权限:$HOME/.rhosts、 $HOME/.shosts、 /etc/hosts.equiv 和 /etc/shosts.equiv。

你可能会想到,rhosts 访问非常不安全,因为它仅依赖于源 IP 地址和主机名,这两者都可以通过各种方式进行欺骗。因此,默认情况下禁用 rhosts 身份验证。shosts 不同:尽管它看起来与 rhosts 的行为相同,但连接主机的身份是通过主机密钥检查来验证的;此外,只有连接主机上的 root 用户才能通过 shost 机制透明地连接。

顺便说一句,结合 rhosts 访问与 RSA 或 DSA 身份验证是一件非常酷的事情,尤其是在使用无密码密钥时。有关将 rhosts 和 shosts 与 SSH 结合使用(无论是否使用 PK 身份验证)的详细信息,请参阅 sshd(8) 手册页。

使用 SSH 的 TCP 端口转发:大众的 VPN!

现在我们来到了回报(以及我通过牺牲对 rhosts/shosts 更完整的讨论而节省空间的章节):端口转发。ssh 为我们提供了一种执行远程登录/shell 和其他命令的机制;scp 添加了文件复制功能。但是 X 呢?POP3 呢?FTP 呢?别担心,SSH 可以保护这些以及大多数其他基于 TCP 的服务!

将 X 应用程序转发回你的远程控制台非常简单。首先,在远程主机上编辑 /etc/ssh/sshd_config 并将“X11Forwarding”设置为“yes”(在 OpenSSH 版本 2x 中,默认值为“no”)。其次,使用你选择的身份验证方法从你的本地控制台打开到远程主机的 ssh 会话。第三,运行你希望运行的任何 X 应用程序。就这样!不用说(我希望),X 必须在你的本地系统上运行;如果是,远程应用程序会将所有 X 输出发送到你的本地 X 桌面。

列表 5. 从远程主机转发 xterm

在发出 xterm & 命令后,一个新的 xterm 窗口将弹出在本地桌面上。我可以同样轻松地(并且仍然可以)运行 Netscape、GIMP 或我的本地 X 服务器可以处理的任何其他东西(当然,这些东西安装在远程主机上)。

X11 是 SSH 硬编码为自动转发的唯一服务类别。可以使用 -L 标志轻松转发其他服务。考虑列表 6 中显示的会话。

列表 6. 使用 ssh 转发 FTP

ssh 行的第一部分看起来很熟悉:我正在使用 SSH 协议 v.2,并使用与本地(mick@homebox)不同的用户名(mbauer)登录到远程主机 (zippy)。-f 标志告诉 ssh 在启动最后一个参数指定的命令后将其自身 fork 到后台,在本例中为 sleep 20。这意味着 ssh 进程将休眠 20 秒,而不是启动 shell 会话。

二十秒足够长的时间来启动我们的隧道 FTP 会话,该会话是通过 -L 标志后面的魔法来启动的。-L 定义了“本地转发”:从客户端系统上的本地 TCP 端口到服务器系统上的远程端口的重定向。“本地转发”遵循语法 local_port_number: remote_hostname: remote_port_number,其中 local_port_number 是本地(客户端)机器上的任意端口,remote_hostname 是服务器(远程)机器的名称或 IP 地址,remote_port_number 是你希望将连接转发到的远程机器上的端口号。

请注意,任何用户都可以使用 ssh 在高端口(大于或等于 1024)上声明本地转发,但只有 root 用户可以在特权端口(小于 1024)上声明它们。继续上面的示例,在 ssh “进入休眠”后,我们返回到本地 shell 提示符,并有 20 秒的时间来建立 FTP 连接。请注意,在列表 6 中,我正在使用 ncftp:这是因为 ncftp 支持 -p 标志,这使我可以告诉它连接到我的本地转发端口 7777。另请注意,我为 ncftp 提供了我的本地系统的名称,而不是远程主机的名称;ssh 将负责将连接路由到远程主机。

20 秒后,ssh 进程将尝试结束,但如果使用本地转发的 FTP 会话仍然处于活动状态,则 ssh 将返回一条消息,说明这种情况,并保持活动状态,直到转发的连接关闭。没有什么可以阻止我们打开登录 shell 而不是运行远程命令(我们只需要省略 -f 标志,然后使用不同的虚拟控制台或窗口来启动 FTP 等)。如果我们确实将 -fsleep 一起使用,我们不必休眠正好 20 秒——休眠间隔并不重要(只要它留出足够的时间来启动转发的连接)。

就此而言,你可以运行任何远程命令来达到所需的暂停,但使用 sleep 是有意义的,因为 sleep 就是为此类事情而设计的。还有一个提示:如果你每次使用 ssh 时都使用给定的本地转发,你可以在你的主目录中的你自己的配置文件 $HOME/.ssh/config 中声明它。语法类似于 -L 标志的语法

LocalForward 7777 zippy.pinheads.com:21

换句话说,在参数名称“LocalForward”之后,你应该有一个空格或制表符、本地端口号、另一个空格、远程主机名或 IP 地址、一个冒号但没有空格和远程端口号。如果你希望它应用于在本地机器上运行的所有 ssh 进程,你也可以在 /etc/ssh/ssh_config 中使用此参数。

这些是 SSH 和 OpenSSH 的一些高级用途。现在我必须与你告别,并将你推荐给手册页以获取更多详细信息和更多功能。前进并加密!

The 101 Uses of OpenSSH: Part II of II
Mick Bauer (mick@visi.com) 是 ENRGI 明尼阿波利斯办事处的安全实践主管,ENRGI 是一家网络工程和咨询公司。自 1995 年以来,他一直是 Linux 爱好者,自 1997 年以来,他一直是 OpenBSD 狂热者,特别喜欢让这些尖端的操作系统在过时的垃圾上运行。Mick 欢迎问题、评论和问候。
加载 Disqus 评论