前言
SSH(Secure Shell)是目前最常用的远程登录协议,它通过加密技术保证了在不安全网络中传输数据的安全性。本文将深入讲解 SSH 的加密原理,并重点介绍如何通过验证服务器指纹来防止中间人攻击。
一、SSH 协议概述
SSH 是一种建立在应用层和传输层基础上的安全协议,主要用于:
- 远程登录
- 文件传输(SCP、SFTP)
- 端口转发
- 隧道代理
与传统的不安全协议(如 Telnet、rlogin)相比,SSH 提供了以下安全保障:
| 安全特性 | 说明 |
|---|---|
| 机密性 | 所有传输数据加密,防止窃听 |
| 完整性 | 数据传输过程不可篡改 |
| 认证性 | 确保连接的是真实的服务器 |
二、SSH 加密原理
SSH 协议的安全性依赖于三种核心加密技术的组合使用:
graph TB
A[SSH 加密体系] --> B[对称加密]
A --> C[非对称加密]
A --> D[哈希算法]
B --> B1[数据传输加密]
B --> B2[算法: AES-256, ChaCha20]
C --> C1[身份认证]
C --> C2[密钥交换]
C --> C3[算法: RSA, ECDSA, Ed25519]
D --> D1[数据完整性校验]
D --> D2[指纹生成]
D --> D3[算法: SHA-256, SHA-512]
2.1 对称加密
对称加密使用同一个密钥进行加密和解密,用于 SSH 会话中实际数据的传输加密。
工作原理:
sequenceDiagram
participant 客户端
participant 服务器
客户端->>服务器: 协商加密算法
服务器->>客户端: 确认算法
客户端->>服务器: 用会话密钥加密数据
服务器->>客户端: 用会话密钥解密数据
常用算法:
| 算法 | 密钥长度 | 安全等级 |
|---|---|---|
| AES-256-GCM | 256 bit | 高 |
| ChaCha20-Poly1305 | 256 bit | 高 |
| AES-128-CTR | 128 bit | 中 |
2.2 非对称加密
非对称加密使用一对密钥(公钥和私钥),主要用于:
- 服务器身份认证 - 服务器持有私钥,公钥分发给客户端
- 密钥交换 - 安全地协商会话密钥
- 用户认证 - 公钥认证登录
常用算法对比:
| 算法 | 密钥长度 | 特点 | 推荐度 |
|---|---|---|---|
| RSA | 2048/4096 bit | 兼容性最好 | ⭐⭐⭐ |
| ECDSA | 256/384/521 bit | 密钥短、速度快 | ⭐⭐⭐ |
| Ed25519 | 256 bit | 安全性高、性能好 | ⭐⭐⭐⭐⭐ |
工作流程:
sequenceDiagram
participant 客户端
participant 服务器
Note over 服务器: 持有私钥
客户端->>服务器: 请求连接
服务器->>客户端: 发送公钥
客户端->>客户端: 验证公钥指纹
客户端->>服务器: 加密挑战数据
服务器->>服务器: 用私钥解密
服务器->>客户端: 返回解密结果
客户端->>客户端: 验证成功
2.3 哈希算法
哈希算法用于:
- 生成密钥指纹(fingerprint)
- 数据完整性校验(HMAC)
- 密钥派生
指纹生成示例:
# SHA-256 指纹(推荐)
ssh-keygen -E sha256 -lf /etc/ssh/ssh_host_ed25519_key.pub
# 输出: 256 SHA256:AbCdEfGhIjKlMnOpQrStUvWxYz...
# MD5 指纹(旧格式)
ssh-keygen -E md5 -lf /etc/ssh/ssh_host_ed25519_key.pub
# 输出: 256 MD5:aa:bb:cc:dd:ee:ff:11:22:33:44:55:66:77:88:99:00
三、SSH 连接建立过程
完整的 SSH 连接建立过程包含多个阶段:
sequenceDiagram
participant C as 客户端
participant S as 服务器
Note over C,S: 阶段1: TCP 连接
C->>S: 发起 TCP 连接 (端口 22)
S->>C: 接受连接
Note over C,S: 阶段2: 协议版本协商
C->>S: SSH-2.0-OpenSSH_9.0
S->>C: SSH-2.0-OpenSSH_9.0
Note over C,S: 阶段3: 密钥交换
C->>S: 发送支持的算法列表
S->>C: 选择算法并发送服务器公钥
C->>S: 验证主机密钥
C->>S: 生成并发送会话密钥
S->>C: 确认密钥交换完成
Note over C,S: 阶段4: 用户认证
C->>S: 请求认证
S->>C: 返回支持的认证方法
C->>S: 发送认证凭据
S->>C: 认证成功
Note over C,S: 阶段5: 会话通信
C->>S: 加密的会话数据
S->>C: 加密的响应数据
四、中间人攻击原理
4.1 攻击场景
graph LR
A[真实客户端] --> B[攻击者]
B --> C[真实服务器]
B -->|伪造密钥| A
B -->|监听/篡改| C
style B fill:#ff6b6b,stroke:#333
攻击流程:
- 攻击者拦截客户端连接请求
- 攻击者伪造服务器公钥发送给客户端
- 客户端建立与攻击者的加密通道
- 攻击者同时与真实服务器建立连接
- 攻击者可以监听、记录、篡改所有通信内容
4.2 SSH 的防御机制
SSH 通过主机密钥验证来防御中间人攻击:
flowchart TD
A[客户端连接服务器] --> B{known_hosts 中<br/>有该主机记录?}
B -->|否| C[显示服务器指纹]
C --> D{用户确认?}
D -->|是| E[保存到 known_hosts]
D -->|否| F[拒绝连接]
E --> G[建立连接]
B -->|是| H{指纹匹配?}
H -->|是| G
H -->|否| I[警告: 密钥变更!]
I --> J{可能原因}
J --> K[服务器重装]
J --> L[中间人攻击]
J --> M[其他异常]
五、防劫持检查方法(重点)
⚠️ 重要提示:指纹验证是防止中间人攻击的核心手段,务必掌握以下验证流程。
5.1 理解 known_hosts 文件
很多用户对 known_hosts 文件存在误解,这里需要明确一点:
known_hosts 存储的是公钥,而非指纹!
graph LR
A[服务器公钥文件] --> B[公钥内容]
B --> C[哈希算法 SHA256]
C --> D[指纹]
E[known_hosts 文件] --> F[存储公钥]
F --> G[需要用 ssh-keygen<br/>提取指纹对比]
style D fill:#4CAF50,stroke:#333
style G fill:#FFC107,stroke:#333
这意味着:
- 服务端:直接从公钥文件生成指纹
- 客户端:需要用
ssh-keygen从known_hosts中提取指纹进行对比
5.2 完整验证流程实战
以下是完整的防劫持验证步骤:
步骤一:服务端获取指纹
在服务器上执行以下命令,获取所有常用密钥类型的指纹:
# 获取所有常用密钥类型的指纹
for key in /etc/ssh/ssh_host_{rsa,ecdsa,ed25519}_key.pub; do
[ -f "$key" ] && ssh-keygen -E sha256 -lf "$key"
done
也可以通过直接在服务器执行
ssh-keyscan localhost | ssh-keygen -E sha256 -lf -命令获取.
输出示例:
256 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 /etc/ssh/ssh_host_rsa_key.pub (RSA)
256 SHA256:p2vJ5zUrHbCkHqMDxG5wHEFKsJ3B5L2Dn6E5kVqWbEY /etc/ssh/ssh_host_ecdsa_key.pub (ECDSA)
256 SHA256:DiD5LKm5V5F6cJ3WqJZzDmF5W6T6C2bDqZsC6uQgT+I /etc/ssh/ssh_host_ed25519_key.pub (Ed25519)
💡 提示:服务器通常有多个密钥类型(RSA、ECDSA、Ed25519),客户端连接时会选择其中一种。
步骤二:客户端获取指纹
在客户端执行以下命令,从 known_hosts 中提取指纹:
# 查看 known_hosts 中存储的公钥指纹
ssh-keygen -E sha256 -lf ~/.ssh/known_hosts
输出示例:
256 SHA256:p2vJ5zUrHbCkHqMDxG5wHEFKsJ3B5L2Dn6E5kVqWbEY (ECDSA)
步骤三:对比指纹
flowchart TD
A[开始验证] --> B[服务端获取指纹]
B --> C[ssh-keygen -lf /etc/ssh/ssh_host_*_key.pub]
A --> D[客户端获取指纹]
D --> E[ssh-keygen -lf ~/.ssh/known_hosts]
C --> F{指纹一致?}
E --> F
F -->|一致| G[✅ 连接安全]
F -->|不一致| H[❌ 可能被劫持]
H --> I[调查原因]
I --> J[服务器重装?]
I --> K[IP变更?]
I --> L[中间人攻击?]
对比要点:
| 对比项 | 服务端 | 客户端 |
|---|---|---|
| 来源 | /etc/ssh/ssh_host_*_key.pub |
~/.ssh/known_hosts |
| 命令 | ssh-keygen -lf <公钥文件> |
ssh-keygen -lf ~/.ssh/known_hosts |
| 输出 | 指纹字符串 | 指纹字符串 |
| 验证 | 指纹相同则安全 | - |
5.3 首次连接验证
首次连接时,known_hosts 文件中不会有该服务器的记录。SSH 会提示你确认:
flowchart TD
A[首次连接 SSH] --> B[服务器发送公钥]
B --> C[客户端显示指纹]
C --> D[用户手动对比]
D --> E{与服务端指纹一致?}
E -->|是| F[输入 yes 确认]
E -->|否| G[输入 no 拒绝]
F --> H[保存公钥到 known_hosts]
H --> I[继续连接]
G --> J[中断连接]
连接提示示例:
The authenticity of host 'server.example.com (192.168.1.100)' can't be established.
ED25519 key fingerprint is SHA256:DiD5LKm5V5F6cJ3WqJZzDmF5W6T6C2bDqZsC6uQgT+I.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
正确做法:
- 提前获取:通过安全渠道(如管理员、官方文档)获取服务器指纹
- 仔细对比:确认显示的指纹与预期一致
- 谨慎确认:指纹一致才输入
yes
5.4 远程预获取指纹
在首次连接前,可以通过 ssh-keyscan 远程获取服务器公钥和指纹:
# 方式一:获取远程服务器公钥
ssh-keyscan your-server-ip 2>/dev/null
# 方式二:直接生成指纹
ssh-keyscan your-server-ip 2>/dev/null | ssh-keygen -E sha256 -lf -
# 方式三:获取特定类型密钥
ssh-keyscan -t ed25519 your-server-ip 2>/dev/null | ssh-keygen -E sha256 -lf -
⚠️ 注意:
ssh-keyscan本身不验证服务器身份,仅适用于你已经通过其他方式确认服务器安全的场景。
5.5 密钥变更警告处理
当服务器密钥变更时,SSH 会发出严重警告:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
安全处理流程:
flowchart TD
A[收到密钥变更警告] --> B[立即停止连接操作]
B --> C{确认服务器状态}
C -->|服务器重装| D[联系管理员获取新指纹]
C -->|更换密钥| E[联系管理员获取新指纹]
C -->|未知原因| F[⚠️ 可能是攻击]
D --> G{新指纹验证}
E --> G
G -->|匹配| H[删除旧记录]
G -->|不匹配| F
H --> I[ssh-keygen -R hostname]
I --> J[重新连接并确认新指纹]
F --> K[进行安全调查]
K --> L[检查网络环境]
K --> M[检查 DNS 解析]
K --> N[联系安全团队]
删除旧记录命令:
# 删除特定主机名
ssh-keygen -R server.example.com
# 删除特定 IP
ssh-keygen -R 192.168.1.100
# 同时删除主机名和 IP
ssh-keygen -R server.example.com
ssh-keygen -R 192.168.1.100
5.6 验证命令速查表
| 场景 | 命令 | 说明 |
|---|---|---|
| 服务端所有指纹 | for k in /etc/ssh/ssh_host_{rsa,ecdsa,ed25519}_key.pub; do [ -f "$k" ] && ssh-keygen -E sha256 -lf "$k"; done |
获取全部密钥指纹 |
| 服务端单类型指纹 | ssh-keygen -E sha256 -lf /etc/ssh/ssh_host_ed25519_key.pub |
指定密钥类型 |
| 客户端已知指纹 | ssh-keygen -E sha256 -lf ~/.ssh/known_hosts |
查看已信任的服务器 |
| 远程获取指纹 | ssh-keyscan host | ssh-keygen -E sha256 -lf - |
预连接验证 |
| 删除信任记录 | ssh-keygen -R hostname |
密钥变更后清理 |
六、最佳实践
6.1 服务器配置
# /etc/ssh/sshd_config
# 禁用弱算法
KexAlgorithms curve25519-sha256,diffie-hellman-group-exchange-sha256
Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# 推荐的密钥类型
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_rsa_key
# 禁用 root 登录
PermitRootLogin no
# 使用公钥认证
PubkeyAuthentication yes
PasswordAuthentication no
6.2 客户端配置
# ~/.ssh/config
# 严格主机密钥检查(重要!)
Host *
StrictHostKeyChecking ask
UserKnownHostsFile ~/.ssh/known_hosts
# 特定服务器配置
Host myserver
HostName server.example.com
User myuser
Port 22
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
⚠️ 警告:不要设置
StrictHostKeyChecking no,这会完全禁用主机验证,使你暴露在中间人攻击风险中。
6.3 安全建议清单
| 项目 | 建议 | 风险等级 |
|---|---|---|
| 密钥类型 | 优先使用 Ed25519 | 高 |
| 密钥长度 | RSA 至少 4096 位 | 高 |
| 主机验证 | 启用 StrictHostKeyChecking | 极高 |
| 密码认证 | 禁用 | 高 |
| 指纹算法 | 使用 SHA-256 | 中 |
| known_hosts 管理 | 定期检查清理 | 中 |
七、总结
SSH 协议通过组合使用对称加密、非对称加密和哈希算法,构建了一个安全的远程访问通道。理解其工作原理,能够帮助我们:
- 正确配置 - 选择安全的加密算法
- 识别风险 - 理解中间人攻击的原理
- 验证身份 - 通过指纹验证服务器真实性
- 处理变更 - 安全地处理密钥变更情况
核心要点:
graph TB
A[防劫持核心] --> B[理解 known_hosts 存储公钥]
A --> C[掌握指纹提取命令]
A --> D[建立验证习惯]
B --> B1[服务端: ssh-keygen -lf 公钥文件]
B --> B2[客户端: ssh-keygen -lf known_hosts]
C --> C1[首次连接前预获取指纹]
C --> C2[变更时验证新指纹]
D --> D1[不盲目输入 yes]
D --> D2[变更警告时调查原因]
style A fill:#4CAF50,stroke:#333
记住一句话:known_hosts 存的是公钥不是指纹,验证时服务端和客户端都要用 ssh-keygen -lf 提取指纹对比。这是防止中间人攻击的最后一道防线。