1995年,赫尔辛基理工大学的研究员Tatu Ylönen在学校的网络上发现了一次密码嗅探攻击。当时的远程登录协议——Telnet、rlogin、rsh——都是明文传输,攻击者可以轻易截获所有凭据。这次事件催生了SSH协议。

三十年后,SSH已经成为系统管理员和开发者的日常工具。但很多人只知道如何使用ssh user@host,却对底层发生了什么一无所知。当你按下回车键后,客户端和服务器之间究竟发生了什么?为什么SSH比Telnet安全?端口转发又是如何工作的?

三层架构的设计哲学

SSH协议采用了分层设计,这种架构并非随意为之。RFC 4251明确定义了三个独立的协议层,每一层都有特定的职责边界。

传输层协议(Transport Layer Protocol)

传输层是整个SSH协议的基石。它的职责包括:服务器认证、加密算法协商、密钥交换、以及建立加密通道。这一层运行在TCP之上,默认监听22号端口——Ylönen选择这个端口号是因为它恰好位于Telnet(23)和FTP(21)之间。

传输层的核心设计目标是在不安全的网络上建立一个安全的通信管道。它需要解决三个问题:如何验证服务器的身份、如何协商加密参数、如何生成用于后续通信的密钥材料。

用户认证协议(User Authentication Protocol)

一旦传输层建立了安全通道,用户认证协议就开始工作。值得注意的是,SSH采用客户端驱动的认证模式——当你看到密码提示时,可能是SSH客户端在询问,而不是服务器。服务器只是响应客户端的认证请求。

RFC 4252定义了多种认证方法:

  • publickey:使用公钥密码学进行认证,支持RSA、ECDSA、Ed25519等密钥类型
  • password:传统的密码认证,包括密码修改功能
  • keyboard-interactive:一种灵活的方法,服务器发送提示,用户输入信息。常用于一次性密码(OTP)或双因素认证
  • hostbased:基于客户端主机的认证,类似于rsh的信任模式

连接协议(Connection Protocol)

连接协议(RFC 4254)定义了"通道"的概念。一条SSH连接可以被复用成多个逻辑通道,每个通道独立传输数据。这种设计允许在单个SSH会话中同时运行多个服务:终端会话、端口转发、X11转发等。

标准通道类型包括:

  • session:用于终端shell、SFTP、SCP等
  • direct-tcpip:客户端到服务器的端口转发
  • forwarded-tcpip:服务器到客户端的端口转发

这种多路复用机制是SSH区别于TLS的重要特征之一。TLS主要用于保护HTTP等协议的传输,而SSH不仅提供加密,还内置了会话管理和端口转发能力。

握手过程:从明文到加密的演变

SSH连接建立的过程可以分解为五个明确的阶段。让我们追踪一个完整的握手过程。

第一步:版本字符串交换

TCP三次握手完成后,客户端和服务器立即交换版本字符串:

客户端发送: SSH-2.0-OpenSSH_9.6
服务器响应: SSH-2.0-OpenSSH_9.6

这个步骤看起来简单,但有几个重要细节:

  1. SSH-1和SSH-2是两个完全不同的协议,不兼容。SSH-1存在严重的设计缺陷——它是一个整体协议,没有分层;使用脆弱的CRC-32完整性检查;不支持通道复用。现代实现应该完全禁用SSH-1。

  2. 版本号1.99表示服务器同时支持SSH-1和SSH-2,这是一种向后兼容标识。

第二步:算法协商

双方交换SSH_MSG_KEXINIT消息,列出各自支持的加密算法列表,按优先级排序:

算法类型 客户端偏好 服务器偏好 最终选择
密钥交换 curve25519-sha256 curve25519-sha256 curve25519-sha256
加密算法 chacha20-poly1305 aes256-gcm aes256-gcm
MAC hmac-sha2-256 hmac-sha2-512 hmac-sha2-256
主机密钥 rsa-sha2-512 ecdsa-sha2-nistp256 ecdsa-sha2-nistp256

选择规则是:客户端按自己的偏好顺序遍历,选择第一个也被服务器支持的算法。这意味着偏好列表的顺序很重要——将更安全的算法放在前面。

第三步:密钥交换(Key Exchange)

这是握手的核心。SSH使用Diffie-Hellman(DH)或其椭圆曲线变体(ECDH)进行密钥交换。以Curve25519为例:

  1. 客户端生成临时密钥对:生成一个临时的公私钥对,这个密钥对只用于本次握手,握手完成后立即销毁。这种"临时性"提供了前向保密(Forward Secrecy)——即使将来有人获取了服务器的长期私钥,也无法解密之前记录的会话。

  2. 客户端发送SSH_MSG_KEX_ECDH_INIT:消息中包含客户端的临时公钥。

  3. 服务器计算共享密钥:服务器收到客户端公钥后,也生成自己的临时密钥对,然后计算共享密钥K。数学上,这基于椭圆曲线的乘法性质:

    $$K = a \cdot B = b \cdot A = a \cdot b \cdot G$$

    其中$A = a \cdot G$是客户端公钥,$B = b \cdot G$是服务器公钥,$G$是曲线的基点。

  4. 服务器生成交换哈希H并签名:交换哈希是对以下所有内容的摘要:

    • 客户端和服务器版本字符串
    • 双方的SSH_MSG_KEXINIT消息
    • 服务器主机公钥
    • 客户端和服务器临时公钥
    • 共享密钥K

    服务器用自己的主机私钥对H签名,生成HS。这个签名有两个目的:证明服务器拥有主机私钥,以及证明服务器能够计算共享密钥。

  5. 服务器发送SSH_MSG_KEX_ECDH_REPLY:包含服务器临时公钥、服务器主机公钥、以及签名HS。

  6. 客户端验证:客户端计算共享密钥K和交换哈希H,验证签名,然后检查服务器主机公钥是否在known_hosts文件中。这就是你第一次连接新服务器时看到的提示:

The authenticity of host 'example.com (192.0.2.1)' can't be established.
ECDSA key fingerprint is SHA256:pnPn3SxExHtVGNdzbV0cRzUrtNhqZv+Pwdq/qGQPZO3.
Are you sure you want to continue connecting (yes/no)?

如果选择yes,服务器公钥会被添加到~/.ssh/known_hosts

第四步:派生会话密钥

共享密钥K本身不直接用于加密数据。双方从K派生出六个密钥:

$$\text{IV}_{\text{C2S}} = \text{HASH}(K \| H \| \text{"A"} \| \text{session\_id})$$$$\text{IV}_{\text{S2C}} = \text{HASH}(K \| H \| \text{"B"} \| \text{session\_id})$$$$\text{Enc}_{\text{C2S}} = \text{HASH}(K \| H \| \text{"C"} \| \text{session\_id})$$$$\text{Enc}_{\text{S2C}} = \text{HASH}(K \| H \| \text{"D"} \| \text{session\_id})$$$$\text{MAC}_{\text{C2S}} = \text{HASH}(K \| H \| \text{"E"} \| \text{session\_id})$$$$\text{MAC}_{\text{S2C}} = \text{HASH}(K \| H \| \text{"F"} \| \text{session\_id})$$

为什么需要这么多密钥?

  • 加密密钥(Enc):用于对称加密算法(如AES)保护数据机密性
  • 完整性密钥(MAC):用于消息认证码防止篡改
  • 初始化向量(IV):确保相同明文加密后产生不同密文

方向分离(C2S和S2C)是为了防止重放攻击。如果只有一个完整性密钥,攻击者可以将客户端发送的消息回放给客户端,客户端会认为这是有效消息。

现代AEAD密码(如chacha20-poly1305、aes-gcm)内部集成了认证功能,不使用单独的MAC密钥。

第五步:完成握手

双方发送SSH_MSG_NEWKEYS消息,宣布从现在开始使用新派生的密钥加密所有后续通信。

至此,传输层协议完成了它的使命——建立了一条加密通道。接下来,用户认证协议开始工作。

SSH握手完整流程

图片来源: Teleport Blog - SSH Handshake Explained

公钥认证的工作原理

公钥认证是SSH最推荐的认证方式。它的工作流程如下:

  1. 客户端声明:客户端发送SSH_MSG_USERAUTH_REQUEST,声明要使用publickey方法,并附带公钥。

  2. 服务器查询:服务器检查该公钥是否在目标用户的~/.ssh/authorized_keys文件中。

  3. 质询签名:如果公钥存在,服务器发送一个质询,要求客户端证明拥有对应的私钥。客户端用私钥签名一段数据(包含会话ID等信息),发送给服务器。

  4. 验证:服务器用公钥验证签名。验证成功则认证通过。

关键点:私钥永远不在网络上传输。这是公钥密码学的精髓——用数学关系证明身份,而不是传输秘密。

SSH Agent的作用

管理多个私钥并频繁输入密码很麻烦。SSH Agent解决了这个问题。它是一个后台进程,将解密后的私钥保存在内存中。

工作流程:

  1. ssh-agent启动后,创建一个Unix域套接字(通常在/tmp/ssh-XXXXXXXXXX/agent.XXXXXX
  2. 环境变量SSH_AUTH_SOCK指向这个套接字
  3. ssh-add命令将私钥添加到agent
  4. SSH客户端通过套接字请求agent进行签名操作

Agent转发允许你在跳板机上使用本地的私钥:

ssh -A user@jumphost
# 在跳板机上可以继续连接其他服务器,使用你本地的私钥
ssh user@internal-server

但要注意安全风险:如果跳板机被攻陷,攻击者可以使用你的agent转发能力。

端口转发:SSH作为隧道工具

SSH的端口转发功能是它区别于其他协议的重要特性。有三种类型:

本地端口转发(Local Port Forwarding)

场景:你想访问内网的数据库,但只能通过跳板机访问。

ssh -L 3306:db.internal:3306 user@jumphost

数据流向:

本地应用 → localhost:3306 → SSH客户端 → [加密隧道] → SSH服务器 → db.internal:3306

从数据库的角度看,连接来自跳板机,而不是你的本地机器。

远程端口转发(Remote Port Forwarding)

场景:你在本地开发了一个Web应用,想让外部用户临时访问。

ssh -R 8080:localhost:3000 user@public-server

数据流向:

外部用户 → public-server:8080 → SSH服务器 → [加密隧道] → SSH客户端 → localhost:3000

这常用于内网穿透,但也可能被滥用为反向代理。出于安全考虑,OpenSSH默认只绑定到服务器的环回地址。要允许外部访问,需要在服务器配置GatewayPorts yes

动态端口转发(Dynamic Port Forwarding)

这会创建一个SOCKS代理:

ssh -D 1080 user@remote-server

配置浏览器使用localhost:1080作为SOCKS代理后,所有HTTP请求都会通过SSH隧道转发。服务器作为代理,替你访问目标网站。

工作原理:

  1. SSH客户端监听本地端口
  2. 应用程序使用SOCKS协议连接到这个端口
  3. SSH客户端解析SOCKS请求,获取目标地址
  4. 通过SSH隧道向服务器请求连接目标
  5. 服务器连接目标并返回结果

SSH端口转发架构

图片来源: Wikipedia - Secure Shell

SSH vs TLS:设计目标的差异

这两个协议都提供加密通信,但设计目标截然不同。

架构对比

特性 SSH TLS
主要用途 远程管理和文件传输 保护Web流量
认证模式 双向认证(服务器和用户) 通常是单向(仅服务器)
会话管理 内置多路复用通道 无内置会话概念
端口转发 原生支持 不支持
证书体系 简单的known_hosts或CA 复杂的PKI体系
用户交互 交互式命令行 非交互式

握手效率

TLS 1.3优化后只需要1-RTT握手,0-RTT恢复。SSH的握手通常需要2-3个RTT。但SSH会话通常是长时间保持的,握手开销相对可以接受。

使用场景划分

选择SSH的场景:

  • 需要远程命令行访问
  • 需要端口转发或隧道
  • 自动化脚本中的安全文件传输
  • 需要交互式会话

选择TLS的场景:

  • Web应用安全
  • API通信加密
  • 需要与浏览器交互
  • 需要大规模证书管理

连接保持与断线处理

SSH连接断开是运维人员的常见困扰。根本原因通常是中间设备(NAT、防火墙)的状态表超时。

TCP Keepalive vs SSH Keepalive

这是两种不同的机制:

TCP KeepaliveTCPKeepAlive yes):

  • 由操作系统TCP栈发送空的TCP ACK包
  • 间隔通常很长(Linux默认是7200秒)
  • 可被伪造,不能用于安全判断

SSH KeepaliveServerAliveInterval/ClientAliveInterval):

  • SSH协议层的保活消息
  • 间隔可配置(建议15-60秒)
  • 经过加密和认证,不可伪造

推荐配置:

# ~/.ssh/config
Host *
    ServerAliveInterval 30
    ServerAliveCountMax 3

这会在30秒无活动后发送保活消息,连续3次无响应则断开连接。

断线重连

autossh是一个有用的工具,可以自动重连断开的SSH会话:

autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" \
    -L 3306:db.internal:3306 user@jumphost

-M 0表示禁用autossh自己的监控端口,使用SSH内置的保活机制。

加密算法的选择与演进

SSH支持的加密算法经历了演进,一些算法已被弃用:

密钥交换算法:

  • 推荐:curve25519-sha256ecdh-sha2-nistp256
  • 弃用:diffie-hellman-group1-sha1(不安全)

加密算法:

主机密钥算法:

  • 推荐:ssh-ed25519rsa-sha2-512
  • 弃用:ssh-rsa(SHA1已不安全)

在OpenSSH 8.8+版本中,ssh-rsa已被默认禁用。如果你的旧服务器只支持ssh-rsa,需要:

ssh -o HostKeyAlgorithms=+ssh-rsa user@old-server

但这只是临时方案,应该升级服务器。

SSH证书:超越密钥管理

管理大规模SSH密钥是运维噩梦。每台服务器需要维护authorized_keys文件,每添加/删除用户都需要更新。

SSH证书提供了一种更优雅的解决方案:

架构

  1. 创建CA密钥对:一个用于签名用户证书,一个用于签名主机证书
  2. 签名主机证书:每台服务器的公钥由CA签名
  3. 签名用户证书:用户公钥由CA签名,包含有效期、权限等信息
  4. 信任配置
    • 客户端信任主机CA:@cert-authority <CA公钥> *
    • 服务器信任用户CA:TrustedUserCAKeys /etc/ssh/user_ca.pub

用户证书示例

ssh-keygen -s user_ca -I [email protected] -V +52w -n admins user_key.pub

这会生成一个有效期52周、可登录admins组服务器的用户证书。

证书中包含的信息:

  • 密钥ID(用于审计)
  • 有效期
  • 主体(用户名)
  • 限制(如禁止端口转发)

优势

  • 集中管理:添加用户只需签发证书,无需更新每台服务器
  • 自动过期:证书到期自动失效,无需手动撤销
  • 审计追踪:证书中的密钥ID可以追溯
  • 权限控制:可以在证书中嵌入访问限制

实际配置建议

客户端配置(~/.ssh/config)

Host *
    # 使用Ed25519密钥
    IdentityFile ~/.ssh/id_ed25519
    
    # 连接保活
    ServerAliveInterval 30
    ServerAliveCountMax 3
    
    # 连接复用
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600
    
    # 安全选项
    HashKnownHosts yes
    VisualHostKey yes

连接复用(ControlMaster)可以显著加速后续连接——第一个连接完成后,后续连接直接复用已有通道,跳过握手。

服务器配置(/etc/ssh/sshd_config)

# 只使用SSH-2
Protocol 2

# 禁用密码认证
PasswordAuthentication no
PubkeyAuthentication yes

# 使用Ed25519主机密钥
HostKey /etc/ssh/ssh_host_ed25519_key

# 限制加密算法
KexAlgorithms curve25519-sha256,ecdh-sha2-nistp256
Ciphers [email protected],[email protected]
MACs hmac-sha2-512,hmac-sha2-256

# 连接限制
MaxAuthTries 3
MaxSessions 10

# 日志
LogLevel VERBOSE

现代挑战与演进

Terrapin攻击(2023)

这是一种针对SSH的新攻击方式,攻击者可以在中间人位置降级连接的安全级别。虽然攻击条件苛刻(需要能够拦截真实SSH会话),但它揭示了SSH协议的一些设计弱点。

OpenSSH 9.6引入了缓解措施,但需要客户端和服务器同时升级才能完全防护。

SSH3:基于HTTP/3的演进

2023年,研究人员提出了SSH3,它将SSH连接协议运行在HTTP/3(QUIC)之上:

  • 更快的握手:从5-7个RTT减少到3个
  • 使用TLS 1.3和QUIC的安全基础设施
  • 支持UDP端口转发
  • 支持X.509证书和OpenID Connect

这是一个实验性项目,尚未成为标准。

云原生环境中的SSH

在Kubernetes等容器环境中,SSH的使用正在发生变化:

  • 临时容器:使用kubectl debug而不是SSH进入Pod
  • 基于证书的访问:Teleport等工具提供短期证书和审计
  • 零信任模型:不再依赖网络边界,而是基于身份的访问控制

从协议到实践

SSH协议的设计体现了工程智慧:分层架构允许各组件独立演进,前向保密保护历史会话,多路复用提高效率,证书机制简化管理。

理解这些底层机制有助于:

  • 诊断连接问题:知道握手失败发生在哪个阶段
  • 安全加固:理解各种配置选项的实际作用
  • 性能优化:合理使用连接复用和端口转发
  • 架构决策:知道何时选择SSH,何时选择其他方案

当你下次输入ssh user@host时,希望你能看到这条命令背后发生的精彩舞蹈——版本协商、密钥交换、身份验证、通道建立——所有这一切在几秒内完成,为你建立了一条安全的通信管道。


参考资料

  1. Ylonen, T., & Lonvick, C. (2006). RFC 4251 - The Secure Shell (SSH) Protocol Architecture. IETF.
  2. Ylonen, T., & Lonvick, C. (2006). RFC 4253 - The Secure Shell (SSH) Transport Layer Protocol. IETF.
  3. Ylonen, T., & Lonvick, C. (2006). RFC 4254 - The Secure Shell (SSH) Connection Protocol. IETF.
  4. SSH Academy. SSH Protocol. https://www.ssh.com/academy/ssh/protocol
  5. Teleport. SSH Handshake Explained. https://goteleport.com/blog/ssh-handshake-explained/
  6. Wikipedia. Secure Shell. https://en.wikipedia.org/wiki/Secure_Shell
  7. Cloudflare. What is SSH? https://www.cloudflare.com/learning/access-management/what-is-ssh/
  8. OpenSSH. Manual Pages. https://www.openssh.com/manual.html
  9. Red Hat. Understanding the SSH Encryption and Connection Process. https://www.redhat.com/en/blog/understanding-ssh-encryption-and-connection-process
  10. Baeldung. Difference Between SFTP, SCP and FISH Protocols. https://www.baeldung.com/linux/sftp-scp-fish-protocols
  11. Smallstep. SSH Agent Explained. https://smallstep.com/blog/ssh-agent-explained/
  12. Terrapin Attack. https://terrapin-attack.com/
  13. SSH3 Project. https://github.com/francoismichel/ssh3