目录

linux ssh

SSH 详解

1. SSH 是什么

SSH(Secure Shell)是一种加密网络协议,用于在不安全的网络上安全地远程登录和执行命令。默认端口 22

SSH 替代了早期不安全的 telnet、rlogin、rsh 等协议,所有传输数据均加密。


2. 密码登录原理

2.1 流程

Client                          Server
  |                               |
  |  ① 发起连接请求               |
  |-----------------------------> |
  |                               |
  |  ② 返回服务器公钥             |
  |<----------------------------- |
  |                               |
  |  ③ 用服务器公钥加密密码       |
  |-----------------------------> |
  |                               |
  |  ④ 用服务器私钥解密,验证密码 |
  |<----- 登录成功/失败 --------- |

2.2 中间人攻击风险

SSH 的服务器公钥是自签发的,没有 CA 机构背书,Client 无法自动验证服务器身份。

第一次连接时会出现:

The authenticity of host 'github.com (20.205.243.166)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

输入 yes 后,服务器公钥指纹会保存到 ~/.ssh/known_hosts,下次连接直接比对,不再提示。

如果服务器公钥变了(重装系统、中间人攻击),会报错:

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

确认是服务器重装导致的,清除旧记录:

ssh-keygen -R 192.168.1.100   # 删除指定主机的记录
# 或手动编辑 ~/.ssh/known_hosts 删除对应行

3. 密钥登录原理

3.1 流程

Client                              Server
  |                                   |
  |  ① 发起连接,声明用户名           |
  |---------------------------------> |
  |                                   |
  |  ② 查找 authorized_keys 中        |
  |     对应公钥,生成随机字符串 R    |
  |     用公钥加密 R,发回            |
  |<--------------------------------- |
  |                                   |
  |  ③ 用本地私钥解密得到 R           |
  |     计算 hash(R + session_id)     |
  |     发送给 Server                 |
  |---------------------------------> |
  |                                   |
  |  ④ Server 验证 hash 是否正确      |
  |<------- 登录成功/失败 ----------- |

私钥从不离开本机,服务器无法得到私钥,安全性高于密码登录。

3.2 生成密钥对

# Ed25519(现代首选,安全性高、速度快)
ssh-keygen -t ed25519 -C "your_email@example.com"

# RSA 4096(老系统不支持 Ed25519 时使用)
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

执行后交互提示:

Enter file in which to save the key (~/.ssh/id_ed25519):  # 回车使用默认路径
Enter passphrase (empty for no passphrase):               # 私钥口令,可为空
Enter same passphrase again:

生成两个文件:

~/.ssh/id_ed25519      ← 私钥,权限必须是 600,绝对不能泄露
~/.ssh/id_ed25519.pub  ← 公钥,可以随意分发

算法对比:

Ed25519 RSA 4096 ECDSA
年代 2013 1977 2005
密钥长度 256 bit 4096 bit 256 bit
安全性 最高
性能 最快
老系统兼容 需要较新 OpenSSH 最兼容 兼容

3.3 部署公钥到服务器

# 方式一:ssh-copy-id(推荐)
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server_ip

# 方式二:手动追加
cat ~/.ssh/id_ed25519.pub | ssh user@server_ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

# 验证免密登录
ssh user@server_ip

authorized_keys 权限必须是 600,目录 .ssh 权限必须是 700,否则 SSH 会拒绝使用(安全策略):

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

4. SSH 配置文件

4.1 客户端配置 ~/.ssh/config

为不同主机设置不同参数,避免每次输入长命令:

# 格式
Host <别名>
    HostName <实际地址>
    User <用户名>
    Port <端口>
    IdentityFile <私钥路径>
    <其他参数>

实际示例:

# GitHub
Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_github

# 公司服务器
Host prod
    HostName 192.168.1.100
    User ubuntu
    Port 22
    IdentityFile ~/.ssh/id_ed25519_work

# 跳板机后的内网服务器
Host inner
    HostName 10.0.0.50
    User root
    ProxyJump jump-server   # 通过跳板机连接

Host jump-server
    HostName 1.2.3.4
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519_work

配置后直接使用别名:

ssh prod         # 等价于 ssh -i ~/.ssh/id_ed25519_work ubuntu@192.168.1.100
ssh inner        # 自动通过跳板机
git clone git@github.com:user/repo.git  # 自动使用指定密钥

常用参数:

参数 说明
HostName 实际主机名或 IP
User 登录用户名
Port 端口(默认 22)
IdentityFile 指定私钥路径
ProxyJump 跳板机(ProxyCommand 的简写)
ServerAliveInterval 心跳间隔(秒),防止超时断开
ServerAliveCountMax 心跳失败最大次数
ForwardAgent 是否转发 ssh-agent
StrictHostKeyChecking 是否严格检查主机指纹

防止长时间不操作断连:

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

4.2 服务端配置 /etc/ssh/sshd_config

修改后需重启服务:systemctl restart sshd

# 修改默认端口(减少暴力破解)
Port 2222

# 禁止 root 直接登录(安全加固必做)
PermitRootLogin no

# 禁止密码登录,只允许密钥登录
PasswordAuthentication no

# 允许公钥认证
PubkeyAuthentication yes

# 公钥文件位置
AuthorizedKeysFile .ssh/authorized_keys

# 限制登录用户
AllowUsers ubuntu deploy

# 登录超时时间(秒)
LoginGraceTime 30

# 最大认证尝试次数
MaxAuthTries 3

# 空闲超时(秒)
ClientAliveInterval 300
ClientAliveCountMax 2

5. ssh-agent(密钥代理)

私钥设置了口令(passphrase)时,每次使用都要输入。ssh-agent 可以将解密后的私钥缓存在内存中,只需输入一次。

# 启动 ssh-agent
eval "$(ssh-agent -s)"
# Agent pid 12345

# 添加私钥到 agent
ssh-add ~/.ssh/id_ed25519
# Enter passphrase for ~/.ssh/id_ed25519: ******
# Identity added: ~/.ssh/id_ed25519

# 查看已缓存的密钥
ssh-add -l

# 删除所有缓存
ssh-add -D

# macOS 可以让 Keychain 记住口令,系统重启后自动加载
ssh-add --apple-use-keychain ~/.ssh/id_ed25519

~/.ssh/config 中配置 macOS 自动加载:

Host *
    UseKeychain yes
    AddKeysToAgent yes
    IdentityFile ~/.ssh/id_ed25519

Agent Forwarding(代理转发)

通过跳板机连接内网服务器时,让内网服务器使用本机的 ssh-agent,无需把私钥复制到跳板机:

ssh -A user@jump-server   # -A 开启 agent forwarding

# 或在 config 中配置
Host jump-server
    ForwardAgent yes

6. 端口转发

SSH 提供三种端口转发,本质都是通过 SSH 隧道转发 TCP 流量。

6.1 本地转发 -L

本地端口的流量,通过 SSH 隧道转发到远端可达的地址

ssh -L [本地地址:]本地端口:目标地址:目标端口 user@跳板机

场景: 服务器 B 的 MySQL 只监听 127.0.0.1:3306,本机想直接连接。

ssh -L 3306:127.0.0.1:3306 user@server_b
# 之后本机连接 127.0.0.1:3306 即可访问 server_b 的 MySQL
mysql -h 127.0.0.1 -P 3306 -u root -p

场景: 通过跳板机访问内网服务。

ssh -L 8080:10.0.0.100:80 user@jump-server
# 本机访问 localhost:8080 → 跳板机 → 内网 10.0.0.100:80

6.2 远程转发 -R

远端端口的流量,转发到本机可达的地址。常用于内网穿透。

ssh -R [远端地址:]远端端口:目标地址:目标端口 user@远端服务器

场景: 本机没有公网 IP,让公网服务器代理流量到本机服务。

# 在本机执行,将公网服务器的 8080 端口流量转发到本机的 3000
ssh -R 8080:localhost:3000 user@公网服务器
# 外部访问 公网服务器:8080 → SSH 隧道 → 本机:3000

服务端需开启 GatewayPorts yes(sshd_config),否则远端端口只监听 127.0.0.1。

6.3 动态转发 -D(SOCKS 代理)

在本地建立 SOCKS5 代理,所有流量通过 SSH 服务器转发,相当于简易 VPN。

ssh -D 1080 user@server
# 配置浏览器/系统代理为 SOCKS5 127.0.0.1:1080
# 之后所有流量走 server 出去

6.4 常用参数组合

# 后台运行,不执行命令,保持连接
ssh -fNL 3306:127.0.0.1:3306 user@server

# -f: 后台运行
# -N: 不执行远程命令
# -C: 压缩数据(低带宽时有用)
# -q: 安静模式,不输出警告

7. SCP 与 SFTP

7.1 SCP(基于 SSH 的文件复制)

# 上传文件到服务器
scp file.txt user@server:/path/to/dest/

# 从服务器下载文件
scp user@server:/path/to/file.txt ./

# 上传目录(-r 递归)
scp -r ./mydir user@server:/path/to/dest/

# 指定端口
scp -P 2222 file.txt user@server:/path/

# 使用 config 别名
scp file.txt prod:/tmp/

7.2 SFTP(SSH 文件传输协议)

sftp user@server

# 常用 SFTP 命令
sftp> ls          # 列出远端文件
sftp> pwd         # 远端当前目录
sftp> lpwd        # 本地当前目录
sftp> get file.txt           # 下载
sftp> put file.txt           # 上传
sftp> get -r mydir/          # 递归下载目录
sftp> mkdir backup           # 创建远端目录
sftp> exit

8. 多跳连接(ProxyJump)

通过跳板机连接内网服务器:

# 命令行方式
ssh -J user@jump-server user@inner-server

# 多级跳板
ssh -J user@jump1,user@jump2 user@target

# 推荐在 config 中配置
Host inner-server
    HostName 10.0.0.50
    User root
    ProxyJump jump-server

9. 安全加固

9.1 服务端加固清单

# 1. 修改默认端口
Port 2222

# 2. 禁止 root 登录
PermitRootLogin no

# 3. 禁止密码登录
PasswordAuthentication no
ChallengeResponseAuthentication no

# 4. 只允许指定用户
AllowUsers deploy ubuntu

# 5. 限制登录来源 IP(/etc/hosts.allow)
sshd: 192.168.1.0/24

# 6. 使用 fail2ban 防暴力破解
apt install fail2ban
# 默认配置:5次失败后封禁10分钟

9.2 客户端安全建议

  • 私钥权限必须是 600,否则 SSH 拒绝使用
  • 为私钥设置 passphrase,配合 ssh-agent 使用
  • 不同服务使用不同密钥,泄露后只影响单一服务
  • 定期轮换密钥
# 检查私钥权限
ls -la ~/.ssh/
# -rw-------  id_ed25519       ← 600 正确
# -rw-r--r--  id_ed25519.pub   ← 644 正确
# drwx------  .ssh/            ← 700 正确

10. 常见问题

10.1 Permission denied (publickey)

# 排查步骤
ssh -v user@server  # 查看详细调试日志

# 常见原因:
# 1. 服务器 authorized_keys 权限不对
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh

# 2. 公钥没有正确写入 authorized_keys
cat ~/.ssh/id_ed25519.pub  # 确认本地公钥
ssh user@server "cat ~/.ssh/authorized_keys"  # 确认服务器内容

# 3. sshd_config 未开启公钥认证
PubkeyAuthentication yes

# 4. SELinux 问题(CentOS/RHEL)
restorecon -Rv ~/.ssh

10.2 Host key verification failed

服务器重装或 IP 复用导致公钥变化:

ssh-keygen -R server_ip_or_hostname
ssh user@server  # 重新确认并添加新指纹

10.3 连接超时断开

# 客户端配置心跳(~/.ssh/config)
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

# 或连接时指定
ssh -o ServerAliveInterval=60 user@server

10.4 使用密钥仍要输密码

可能是服务器要求输入的是私钥 passphrase,不是系统密码:

# 添加到 ssh-agent 后只需输入一次
ssh-add ~/.ssh/id_ed25519

# macOS 永久保存到 Keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519

10.5 端口转发连接断开

# 加 -o 参数保持连接
ssh -fNL 3306:127.0.0.1:3306 \
    -o ServerAliveInterval=60 \
    -o ExitOnForwardFailure=yes \
    user@server

# 或使用 autossh 自动重连
brew install autossh
autossh -M 0 -fNL 3306:127.0.0.1:3306 user@server

11. 实用技巧

# 执行远程命令,不进入交互
ssh user@server "df -h && free -m"

# 将本地命令输出传给远端
tar czf - ./mydir | ssh user@server "cat > backup.tar.gz"

# 远端文件传到本地(不用 scp)
ssh user@server "cat /etc/nginx/nginx.conf" > nginx.conf

# 测试 SSH 连接(不执行命令)
ssh -T git@github.com

# 查看 SSH 登录日志
tail -f /var/log/auth.log      # Ubuntu/Debian
tail -f /var/log/secure        # CentOS/RHEL
journalctl -u sshd -f          # systemd