Files
cs-note/hhs/NETWORK/05-应用层协议/06-SSH与邮件协议.md
T
2026-05-24 11:42:38 +08:00

206 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [计算机网络, SSH, SFTP, SCP]
create time: 2026-05-18 03:20
---
# SSH 远程安全登录
## 概述
SSH(Secure Shell)是替代 Telnet、rlogin 等明文协议的加密外壳工具。它不仅仅用于远程登录——SSH 的隧道转发能力使其成为网络安全的瑞士军刀。
## SSH 架构三层模型
```
┌─────────────────────────────────┐
│ Application Layer │ ← SSH-CONNECT, SSH-USERAUTH, SSH-CONNECTION
│ • scp / sftp │ 每个子协议独立协商版本
│ • port forwarding │
│ • X11 forwarding │
├─────────────────────────────────┤
│ Transport Layer │ ← Host-key auth + encryption + integrity
│ • Server host-key authentication│ 一旦建立隧道,所有上层协议自动加密
│ • Server/pubkey exchange │
│ • Symmetric encryption │ 默认 AES-128-GCM or ChaCha20-Poly1305
│ • HMAC integrity │
├─────────────────────────────────┤
│ User Authentication Layer │ ← 多种认证方式
│ • password │ SSH-CONN USER_AUTH_REQUEST
│ • public key │
│ • keyboard-interactive │ MFA/TOTP
│ • GSSAPI (Kerberos) │
│ • OS Login / Certificate │
└─────────────────────────────────┘
```
## SSH 密钥交换过程
```mermaid
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: Phase 1: Key Exchange (Diffie-Hellman)
C->>S: KEXINIT (supported ciphers, DH groups, MACs, compressions)
S-->>C: KEXINIT (negotiated algorithms)
Note over C,S: 服务器选择双方都支持的最强算法
S->>C: Server Key Exchange (DH params)
S->>C: Server Host Key (RSA/ED25519) + signature
Note over C: Verify host key fingerprint!
C->>S: Client Key Exchange (DH response)
Note over C,S: Both compute shared secret → derive session keys
Note over C,S: Phase 2: User Authentication
C->>S: SSH_USERAUTH_REQUEST "root" "password"
S-->>C: SSH_USERAUTH_SUCCESS ✅
Note over C,S: Phase 3: Channel Open
C->>S: SSH_CHANNEL_OPEN "session"
S-->>C: SSH_CHANNEL_OPEN_CONFIRMATION
```
### SSH 支持的公钥算法
| 算法 | 密钥大小 | 安全性等级 | 备注 |
|------|---------|-----------|------|
| **ed25519** | 32 bytes | ~128 bits | ✅ **推荐**,EdDSA 椭圆曲线,速度快 |
| rsa | 4096 bits | ~128 bits | 通用兼容,但体积大 |
| ecdsa | 384 bits | ~128 bits | NIST 曲线,争议因 DualECDRBG |
| dsa | 1024 bits | ~80 bits | ❌ **已弃用**,OpenSSH 7.0+ 禁用 |
| ssh-rsa (SHA-1) | variable | 弱 | ❌ OpenSSH 8.8+ 默认禁用 |
```bash
# 生成 ed25519 密钥
$ ssh-keygen -t ed25519 -C "alice@example.com"
# 或带注释和自定义路径
$ ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work -C "work@company.com"
# 复制公钥到远程服务器
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-server
# 手动添加
$ cat ~/.ssh/id_ed25519.pub | ssh user@remote 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'
```
## SSH 端口转发(Tunneling)
### 本地端口转发(Local Forwarding)⭐
```bash
# 通过 jump server 访问内网数据库
ssh -L 3307:db.internal:3306 user@jump.example.com
# 等效命令: ssh -L <local_port>:<dest_host>:<dest_port> <jump_host>
# 本地 localhost:3307 → SSH 隧道 → jump.example.com → db.internal:3306
```
```
Client Jump Server DB Server
──────── ─────────── ─────────
localhost:3307 ──→ [SSH Tunnel] ──→ db.internal:3306
↑ ↑
MySQL CLI SSH 加密通道 🔒
```
### 远程端口转发(Remote Forwarding)
```bash
# 让外网访问我本地服务(反向穿透 NAT)
ssh -R 8080:localhost:3000 user@public-server
# 我的 Mac:3000 ← SSH -R ← public-server:8080
# 任何人访问 public-server:8080 都能到我的本地服务!
```
### 动态端口转发(SOCKS Proxy)
```bash
# 创建 SOCKS5 代理
ssh -D 1080 user@bastion
# 浏览器设置 SOCKS proxy: 127.0.0.1:1080
# → 所有流量经过 bastion 转发
```
## SSH 配置文件
```bash
# ~/.ssh/config
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Host bastion
HostName 203.0.113.5
User deploy
IdentityFile ~/.ssh/id_ed25519_bastion
Port 2222
Host internal-*
ProxyJump bastion
User admin
IdentityFile ~/.ssh/id_ed25519_internal
ServerAliveInterval 60
ServerAliveCountMax 3
# 使用: ssh internal-webapp1 (自动经 bastion 跳转)
```
### 关键参数说明
| 参数 | 说明 |
|------|------|
| `ProxyJump` | 通过堡垒机跳转 |
| `ServerAliveInterval` | 客户端发送心跳间隔(秒),防防火墙超时 |
| `ServerAliveCountMax` | 最多连续无响应次数后断开 |
| `IdentitiesOnly` | 仅使用指定的 identity file,不尝试其他密钥 |
| `StrictHostKeyChecking` | `ask`(默认) / `no`(不检查) / `accept-new`(首次接受) |
## Go 中的 SSH
```go
import (
"golang.org/x/crypto/ssh"
"os"
)
// 读取私钥文件
key, err := os.ReadFile("~/.ssh/id_ed25519")
if err != nil { panic(err) }
signer, err := ssh.ParsePrivateKey(key)
if err != nil { panic(err) }
config := &ssh.ClientConfig{
User: "admin",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 生产环境用 ssh.FixedHostKey()
}
client, err := ssh.Dial("tcp", "server.example.com:22", config)
if err != nil { panic(err) }
// 执行远程命令
session, _ := client.NewSession()
defer session.Close()
output, _ := session.CombinedOutput("uname -a; uptime")
fmt.Println(string(output))
// SFTP client
sftpClient, _ := sftp.NewClient(client)
defer sftpClient.Close()
```
## 关联笔记
- [[hhs/NETWORK/HTTPS与TLS握手]] — SSH 也使用非对称加密 + 对称加密混合模式
- [[hhs/NETWORK/NAT原理与应用]] — SSH 端口转发可穿透 NAT
- [[hhs/NETWORK/MAC地址与广播域]] — SSH 在局域网内的常见部署场景