OpenSSH 协议内幕
在日常 Linux 计算中,有比 SSH 更常用的程序吗?我对此表示怀疑。它不仅坚如磐石、安全可靠且功能多样,而且还极其易于使用且功能丰富。由于其算法和协议都是最先进的,并且其实施是开放的,可以进行同行评审,因此我们可以对 SSH 的密码学完整性感到放心。然而,SSH 确实存在弱点;尽管它们大多数源于社会工程学,并且解决诸如 X11 等损坏的协议构成了巨大的挑战。
由于 UNIX 将强大的工具以通用方式组合在一起的理念,SSH 只需几行 C 代码即可创造奇迹。
SSH 充当安全通道,它使远程系统看起来像本地系统,而本地系统在远程端看起来像本地系统。它可以用于远程命令执行,无论是否使用 pty,并且可以用于多路复用多个 TCP 和 X11 会话。它还可以用于通过安全 SSH 隧道隧道化不安全的协议,例如 POP3 或 SMTP。此外,它可以在一定程度上用于安全地隧道化 FTP。
让我们从整体方案开始。
如图 1 所示,OpenSSH 由三个关键层组成。底层 ssh-transport 是参与所有加密操作的最关键组件,例如密钥交换、定期重新密钥、以各种方式防止攻击等等。
ssh-transport 之上的层 ssh-userauth 负责对服务器端的 sshd 守护进程进行最终用户身份验证。请记住,SSH 是双向身份验证的。客户端 SSH 程序使用 ssh-transport 协议验证 sshd 服务器守护进程。身份验证完成后,密钥交换完成,并建立安全连接。在此之后,用户身份验证在 ssh-userauth 层中进行。
ssh-userauth 提供了很大的灵活性,因为用户可以通过多种方式向服务器进行身份验证,从智能卡上的私钥到简单的用户名/密码身份验证。一旦通过,ssh-connection 层将建立安全通道,用于执行远程命令或获取交互式登录 shell。
如图 1 所示,ssh-connection 层能够通过单个 ssh-userauth 层和下方的传输堆栈层多路复用任意数量的并发独立安全会话。SSH 的所有魔力——从本地到远程以及从远程到本地转发任意 TCP 端口、充当 SOCKS 代理、转发 X11 连接、建立 VPN 隧道、执行带或不带 pty 的远程命令——都是通过 ssh-connection 层完成的。
SSH 协议内置了流控制。每个安全通道都有一个单独分配的窗口大小。由于 SSH 在可靠的 TCP 层之上运行,因此这没有太大作用。至少,它不像 TCP 窗口机制那样关键。大多数关键的通道打开/关闭消息和其他终止消息不占用任何窗口空间。
由于所有消息都经过加密和完整性保护,因此没有人可以解释这些消息。有一种特殊的 SSH_MSG_IGNORE 消息类型,可用于击败流量分析攻击。这些攻击旨在弄清楚何时数据在线路上流动以及传输了多少数据。
当然,SSH 还附带许多其他优点,用于发送安全 KEEPALIVE 消息、将 stdin 重定向到 /dev/null 以用于专门的 X 窗口应用程序等等。
现在,让我们看一下示例 SSH 会话和典型的消息交换(图 2)。
这是一个典型的未加密 SSH 数据包
byte SSH_MSG_CHANNEL_REQUEST uint32 recipient channel string "pty-req" boolean want_reply string TERM environment variable value (e.g., vt100) uint32 terminal width, characters (e.g., 80) uint32 terminal height, rows (e.g., 24) uint32 terminal width, pixels (e.g., 640) uint32 terminal height, pixels (e.g., 480) string encoded terminal modes
大多数字段都是不言自明的。顶部两个字段始终存在于所有消息中。有效负载数据包(用户键入的内容和来自服务器的响应)都使用 SSH_MSG_DATA 消息类型进行传输。
每个数据包都有一个标头,用于描述有效负载的内容(消息类型)和目标通道。
某些消息不需要来自另一端的响应,因为底层不仅可靠而且防篡改。但是,来自客户端的大多数请求都有来自服务器的相应响应。
现在,让我们深入了解 SSH 密钥交换协议的详细信息,因为这是最关键的组件,它解释了 SSH 的安全性和受欢迎程度。
图 3 显示了加密、压缩和完整性保护所需的数据操作。当然,我们需要保护自己免受重放攻击。为此,每个数据包都有一个隐式序列号,它从 0 开始,一直到 232,然后绕回。由于序列号是哈希的,因此它可以是顺序的,攻击者永远无法猜测哪个输入会导致哪个哈希。
OpenSSH 密钥的关键组件是
哈希:H。
共享密钥:K。
会话 ID:session_id。
SSH 使用上述组件来派生以下加密向量和密钥
客户端到服务器的初始化向量。
服务器到客户端的初始化向量。
客户端到服务器的加密密钥。
服务器到客户端的加密密钥。
客户端到服务器的 MAC 密钥。
服务器到客户端的 MAC 密钥。
用于派生上述向量和密钥的公式取自 RFC 4253。在下文中,|| 符号表示连接,K 编码为 mpint,“A”作为字节,session_id 作为原始数据。任何字母,例如“A”(在引号中)表示单个字符 A 或 ASCII 65。
客户端到服务器的初始 IV:HASH(K || H || “A” || session_id)。
服务器到客户端的初始 IV:HASH(K || H || “B” || session_id)。
客户端到服务器的加密密钥:HASH(K || H || “C” || session_id)。
服务器到客户端的加密密钥:HASH(K || H || “D” || session_id)。
客户端到服务器的完整性密钥:HASH(K || H || “E” || session_id)。
服务器到客户端的完整性密钥:HASH(K || H || “F” || session_id)。
简单,对吧?
然而,不简单的是弄清楚 K 和 H 参数。
HASH 通常是 SHA1 哈希机制,但它也可以是其他机制。
典型的密码算法是 CBC 模式下的 AES 或 DES3。MAC 是 MD5 或 SHA1 哈希算法与密钥的组合。这里有四种选择
hmac-sha1
hmac-md5
hmac-sha1-96
hmac-md5-96
实际上,sha1 在当今世界有点弱,因为可能发生碰撞攻击。如今哈希的时代精神是 sha512,但通过适当的重新密钥和其他内置的智能,这应该不是问题。
请记住,哈希的长度是恒定的,因此 hmac-sha1 的长度为 20 字节,hmac-md5 的长度为 16 字节,而其他两个的固定长度均为 12 字节。
好的,现在介绍 kex 阶段的一些数学和密码学技巧。
我们知道如何计算各个加密和 MAC 密钥,前提是我们使用上面的简单公式推导出基本参数。但是,我们如何以安全、经过身份验证的方式首先获得这些参数呢?
现在,我们需要了解 OpenSSH 如何使用 diffie-hellman-group14 和 diffie-hellman-group1 字段来为匿名密钥协商派生 DH 生成器和 DH 模数。但是,这使我们容易受到多种中间人攻击和其他主动攻击。为了阻止这种情况,我们使用已知且受信任的服务器公钥来验证密钥交换。密钥交换数据的身份验证只不过是用私钥签名。并且,OpenSSH 通常为此目的使用 ssh-dsa 或 ssh-rsa 密钥。
换句话说,DH 和 RSA/DSS 密钥的组合用于身份验证并派生秘密参数 K、H 和 session_id。session_id 只是第一次密钥交换的哈希值。16 字节的随机 cookie 也用于防止重放和其他中间人攻击。
这是派生 H 的公式
H = hash(V_C || V_S || I_C || I_S || K_S || e || f || K)
哈希通常是 SHA1 哈希算法。
V_C 和 V_S 是客户端和服务器标识字符串。
I_C 和 I_S 是刚刚交换的客户端和服务器 SSH_MSG_KEXINIT 消息。
现在,我们剩下计算 e、f 和 K;e 和 f 是用于求幂的 DH 参数
e = gx 模 p
f = gy 模 p
K = ey 模 p
在这里,p 是来自 DH 生成器字段的素数。并且,x 和 y 由客户端和服务器任意选择。请记住,DH 使用简单的数学原理 abc = acb = abc 工作。
现在,我们拥有计算密钥所需的一切。
所有这些加密参数的优点是它们在每个会话后都会被丢弃。唯一重用的参数是服务器 RSA/DSA 密钥,但由于我们在计算中添加了随机 cookie,因此攻击者很难以密码方式破解 SSH。
在继续之前,让我们看一下 OpenSSH 系列。

图 4. OpenSSH 星系中的恒星
如图 4 所示,在宏伟的计划中有很多可执行文件和参与者。但是,相互作用并不复杂。我上面讨论的一切实际上都是由 SSH 和 sshd 组件(分别为客户端和服务器)实现的。其他组件很少用于密钥生成、代理转发等。
sftp-server 是 SSH 的子系统。这是一种类似 FTP 的协议,但它与损坏的 FTP 协议不同,它非常安全高效。
scp 是一种非常流行且方便的文件传输机制,建立在 SSH 基础设施之上。由于完整性保护内置于 SSH 有线协议中,因此文件完整性得到保证。但是,它没有断点续传功能,因此您必须将其与 rsync 一起使用才能获得该功能。
现在,让我们看一下 SSH 帮助我们防范的攻击类型和威胁模型。
任何密码协议最关键的组件之一是随机数生成器的质量。由于计算机是确定性设备,因此获取真正的随机数据是一项挑战。熵的常见来源包括磁盘访问、键盘和鼠标输入、进程生命周期等等。大量传统的 UNIX 程序都依赖于 gettimeofday(2) 系统调用。SSH 还使用可靠的机制来检查数据池的随机性。
一种特定于 SSH 的有趣攻击是使用控制字符序列来终止会话并干扰 pty 交互,因此我们必须过滤掉可疑的字符序列。
最关键且不幸的是 SSH 最薄弱的环节是服务器/主机身份验证。现实情况和典型的用户疏忽证明,每当向我们的信任列表添加新主机密钥时,我们都会直接说是。正在努力使之更加安全和容易。如果不能确保这一点,则可能会发生不同类型的中间人攻击。
资源
OpenSSH:www.openssh.org
SSH 协议架构:www.ietf.org/rfc4251.txt
ssh-userauth:www.ietf.org/rfc4252.txt
ssh-transport:www.ietf.org/rfc4253.txt
ssh-connect:www.ietf.org/rfc4254.txt
Girish Venkatachalam 是一位密码学家,拥有近十年的各种现代 UNIX 系统工作经验。他从头开始为路由器开发了 Nucleus OS 上的 IPSec,并使用过 Apache、OpenSSL 和 SSH 的内部组件。可以通过 girish1729@gmail.com 与他联系。