---
title: SSH 协议
url: https://doc.liz6.com/networking/12-security-protocols/01-ssh-protocol
locale: zh
area: networking
tags:
- networking
- 安全协议
date: 2026-06-30
modified: 2026-07-11
description: SSH 不只是"加密的 telnet"——传输层(密钥交换+加密)、认证层(公钥/密码/证书)、连接层(端口转发/X11/SFTP 多路复用)三层分离。端口转发让 SSH 变成了"瑞士军刀"。
---

# SSH 协议

> SSH 不只是"加密的 telnet"——传输层(密钥交换+加密)、认证层(公钥/密码/证书)、连接层(端口转发/X11/SFTP 多路复用)三层分离。端口转发让 SSH 变成了"瑞士军刀"。

## 概述

SSH（Secure Shell, 1995/2006）是 Unix/Linux 系统管理的核心工具。协议分为三层：Transport Layer 做密钥交换和加密、User Authentication Layer 做身份验证（公钥/密码/TOTP）、Connection Layer 在单一 TCP 连接上多路复用 shell/port forwarding/SFTP 等 channel。SSH 的端口转发（-L/-R）可以作为简易 VPN——通过加密隧道将远程端口映射到本地。SSH 2.0 修复了 SSH 1.x 的严重安全问题，是现代唯一安全的版本。

## 三层架构

```
Connection Protocol (RFC 4254):  channels (shell, exec, forward, SFTP)
User Authentication (RFC 4252):  password, publickey, keyboard-interactive
Transport Layer (RFC 4253):      key exchange, encryption, server auth
TCP
```

## Transport Layer: 密钥交换 (curve25519-sha256)

<svg viewBox="0 0 720 330" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="SSH 密钥交换五步走:版本交换、算法协商、DH交换、派生密钥、验证主机身份">
  <defs><marker id="sshkexah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="330" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">SSH 密钥交换五步走(curve25519-sha256)</text>

  <rect x="24" y="48" width="214" height="26" rx="6" fill="#4f46e5"/>
  <text x="131" y="65" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">① 建立连接与协商算法</text>
  <rect x="258" y="48" width="214" height="26" rx="6" fill="#0d9488"/>
  <text x="365" y="65" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">② DH 密钥交换</text>
  <rect x="492" y="48" width="204" height="26" rx="6" fill="#22c55e"/>
  <text x="594" y="65" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">③ 派生密钥与验证主机</text>
  <line x1="238" y1="61" x2="258" y2="61" stroke="#475569" stroke-width="1.6" marker-end="url(#sshkexah)"/>
  <line x1="472" y1="61" x2="492" y2="61" stroke="#475569" stroke-width="1.6" marker-end="url(#sshkexah)"/>

  <rect x="32" y="86" width="198" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="131" y="103" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">版本交换</text>
  <text x="131" y="121" text-anchor="middle" font-size="10" fill="#4f46e5">C ↔ S 互换 SSH-2.0-OpenSSH_9.6</text>
  <rect x="32" y="140" width="198" height="56" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="131" y="157" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">算法协商(KEXINIT)</text>
  <text x="131" y="174" text-anchor="middle" font-size="10" fill="#4f46e5">候选表:kex / host_key</text>
  <text x="131" y="188" text-anchor="middle" font-size="10" fill="#4f46e5">加密算法 / MAC 算法</text>

  <rect x="266" y="86" width="198" height="46" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="365" y="103" text-anchor="middle" font-size="12" font-weight="700" fill="#115e59">C → S: KEX_ECDH_INIT</text>
  <text x="365" y="121" text-anchor="middle" font-size="10" fill="#0f766e">发送临时公钥 Q_C</text>
  <rect x="266" y="140" width="198" height="66" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="365" y="157" text-anchor="middle" font-size="12" font-weight="700" fill="#115e59">S → C: KEX_ECDH_REPLY</text>
  <text x="365" y="174" text-anchor="middle" font-size="10" fill="#0f766e">K_S(主机公钥)+ Q_S(临时公钥)</text>
  <text x="365" y="188" text-anchor="middle" font-size="10" fill="#0f766e">signature over H,证明持有私钥</text>

  <rect x="500" y="86" width="188" height="58" rx="6" fill="#dcfce7" stroke="#4ade80"/>
  <text x="594" y="103" text-anchor="middle" font-size="12" font-weight="700" fill="#166534">计算共享密钥</text>
  <text x="594" y="121" text-anchor="middle" font-size="10" fill="#15803d">shared secret → H = session_id</text>
  <text x="594" y="135" text-anchor="middle" font-size="10" fill="#15803d">派生 IV / 加密key / MAC key(双向)</text>
  <rect x="500" y="154" width="188" height="66" rx="6" fill="#ffedd5" stroke="#f97316"/>
  <text x="594" y="171" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">客户端验证 K_S</text>
  <text x="594" y="188" text-anchor="middle" font-size="10" fill="#c2410c">首次连接:核对指纹</text>
  <text x="594" y="202" text-anchor="middle" font-size="10" fill="#c2410c">写入/比对 known_hosts</text>

  <rect x="40" y="242" width="640" height="70" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="264" text-anchor="middle" font-size="12.5" fill="#3730a3">验证顺序是关键:签名先证明服务器持有 K_S 私钥,known_hosts 比对再防中间人劫持。</text>
  <text x="360" y="284" text-anchor="middle" font-size="12.5" fill="#3730a3">首次连接的信任依赖 TOFU(Trust On First Use)——这是 SSH 唯一无法自动化验证的环节。</text>
</svg>

## User Authentication

```
方法 (按顺序尝试):
  1. publickey: 客户端: sign(session_id + request + publickey, privatekey)
     服务器: verify → SSH_MSG_USERAUTH_SUCCESS
  2. keyboard-interactive: TOTP 或 challenge-response
  3. password: 在加密通道中传输 (安全)
```

## Connection Layer: Channel 多路复用

```
每 channel 独立:
  SSH_MSG_CHANNEL_OPEN: type="session" → channel 0
  SSH_MSG_CHANNEL_OPEN: type="direct-tcpip" → channel 1 (port forwarding)

Channel types:
  session:         shell, exec, subsystem (SFTP)
  direct-tcpip:    本地端口转发 (-L)
  forwarded-tcpip: 远程端口转发 (-R)
```

## Port Forwarding

```
Local (-L 8080:internal:80): SSH 客户端监听:8080 → tunnel → SSH服务端连 internal:80
Remote (-R 3000:localhost:22): SSH 服务端监听:3000 → tunnel → SSH客户端连 localhost:22
```

## 参考

- **RFC**: 4251-4254, 5656, 8731
- **源码**: OpenSSH (`sshconnect2.c` for auth, `channels.c` for channels)

*Keywords: SSH, transport, kex, publickey, channel, port forwarding, SFTP, known_hosts*
