0%

SSH 协议的加密原理以及防劫持检查方法

前言

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 非对称加密

非对称加密使用一对密钥(公钥和私钥),主要用于:

  1. 服务器身份认证 - 服务器持有私钥,公钥分发给客户端
  2. 密钥交换 - 安全地协商会话密钥
  3. 用户认证 - 公钥认证登录

常用算法对比:

算法 密钥长度 特点 推荐度
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

攻击流程:

  1. 攻击者拦截客户端连接请求
  2. 攻击者伪造服务器公钥发送给客户端
  3. 客户端建立与攻击者的加密通道
  4. 攻击者同时与真实服务器建立连接
  5. 攻击者可以监听、记录、篡改所有通信内容

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-keygenknown_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])?

正确做法:

  1. 提前获取:通过安全渠道(如管理员、官方文档)获取服务器指纹
  2. 仔细对比:确认显示的指纹与预期一致
  3. 谨慎确认:指纹一致才输入 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 协议通过组合使用对称加密、非对称加密和哈希算法,构建了一个安全的远程访问通道。理解其工作原理,能够帮助我们:

  1. 正确配置 - 选择安全的加密算法
  2. 识别风险 - 理解中间人攻击的原理
  3. 验证身份 - 通过指纹验证服务器真实性
  4. 处理变更 - 安全地处理密钥变更情况

核心要点:

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 提取指纹对比。这是防止中间人攻击的最后一道防线。

参考资料

  • 本文作者: 6x
  • 本文链接: https://6xyun.cn/article/231
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处!