SSH 是什么?

SSH 是一种网络安全协议,用于计算机之间的加密通讯。SSH 的英文全称是 Secure Shell。SSH 对所有传输的数据进行加密,能够有效防止明文传送密码等敏感信息导致的信息泄露,防止DNS欺骗和IP欺骗。SSH 可以代替不安全的 telnet(终端仿真协议),可以为 FTP 等网络应用提供一个安全的“通道”。

OpenSSH 是 SSH 协议的免费开源实现,包含了一组相关的应用程序,最早在 OpenBSD 中被实现,现已成为各种 Linux 发行版默认的 SSH 工具,包括 Mac,且已被 Windows 10 所采用。

SSH 还是 Struts + Spring + Hibernate,一种基于Java技术的Web应用开发框架。

SSH 基本用法

SSH主要用于远程登录。假设以用户名user登录远程主机host,只要一条简单的命令:

$ ssh user@host

SSH默认连接远程端口22,即登录请求默认会被送进远程主机的22端口,可使用p参数改变端口:

$ ssh user@host -p 2020

基本上,你总是可以在Mac或Linux终端中直接使用ssh命令,而在Windows环境下,情况比较复杂,可能需要安装某种 SSH Client,如 MobaXterm、Bitvise SSH Client、Terminus 等。

SSH 工作过程

为实现SSH的安全连接,在整个通讯过程中,客户端与服务端要经历以下阶段:

  • 连接请求:客户端向服务端发起SSH连接请求;

  • 版本协商:双方协商确定使用的SSH版本。目前有 SSH1 和 SSH2 两个版本;

  • 密钥和算法协商:双方根据本端和对端支持的算法列表(SSH支持多种算法),协商确定最终使用的算法,并且生成会话密钥和会话ID;

  • 认证阶段:客户端发起认证请求,服务端对客户端进行认证;

  • 会话请求:登录认证通过后,客户端向服务端发送会话请求;

  • 会话阶段:会话请求通过后,双方进入交互阶段。

SSH 登录认证

使用SSH登录时,对于首次登录的远程主机,客户端会要求用户通过“公钥指纹”确认主机的真实性。当用户核对无误,回答 yes 后,远程主机的公钥被确认,并被保存在客户端$HOME/.ssh/known_hosts文件中。下次连接这台主机时,客户端就会认出其公钥,从而跳过主机确认,直接进入登录认证。

通过修改配置文件,可以跳过 known_hosts 询问。(详见:SSH配置文件)

SSH 登录认证方式主要有两种: 基于密码的认证和基于密钥的认证。

基于密码的认证

客户端要求用户输入登录密码。如果密码正确,就可以登录了。

为保证安全,密码是加密传输的,认证过程如下:

  • 远程主机收到用户的认证请求,把自己的公钥发给客户端。
  • 客户端使用远程主机的公钥,将登录密码加密后,发回远程主机。
  • 远程主机用自己的私钥解密登录密码,如果密码正确,就同意用户登录。

这个过程本身是安全的,但在实施的时候存在一个风险:如果有人截获了登录请求,然后冒充远程主机,将伪造的公钥发给用户,那么用户很难辨别真伪(远程主机可能需要公示其公钥指纹,以便用户核对)。因为不像 https 协议,SSH协议的公钥都是自己签发的,是没有证书中心(CA)公证的。

可以设想,如果攻击者插在用户与远程主机之间(比如在公共的wifi区域),用伪造的公钥,获取用户的登录密码。再用这个密码登录远程主机,那么SSH的安全机制就荡然无存了。这种风险就是著名的“中间人攻击”(Man-in-the-middle attack)。

基于密钥的认证

密钥认证不需要输入密码,可实现免密登录,但必须先将客户端公钥保存到远程主机的特定文件中。为此,可以使用 ssh-keygen 生成密钥对,然后将公钥保存到远程主机中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 使用 RSA 算法生成密钥对
ssh-keygen -t rsa -C "your_email@domain.com" # or
ssh-keygen -t rsa -b 4096 -C "your_email@domain.com"
# 在 $HOME/.ssh 目录下生成公钥私钥两个文件:id_rsa.pub 和 id_rsa

# 使用 ecdsa 算法生成密钥对
ssh-keygen -t ecdsa -b 256 -C "your_email@domain.com"

# 复制公钥到远程主机
ssh-copy-id -i ~/.ssh/id_rsa.pub user@host  # or
ssh user@host 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys' < ~/.ssh/id_rsa.pub  # or
cat ~/.ssh/id_rsa.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" # or
cat ~/.ssh/id_rsa.pub | ssh user@host "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

# 必要时可通过剪贴簿粘贴
clip < ~/.ssh/id_rsa.pub  # Windows
pbcopy < ~/.ssh/id_rsa.pub  # Mac/Linux

密钥认证的完整过程如下:

  • 服务端向客户端发送一段随机字符串;
  • 客户端用自己的私钥加密随机字符串,发回服务端;
  • 服务端用事先储存的客户端公钥对加密随机字符串进行解密;
  • 如果解密成功,证明客户端是可信的,直接允许登录,不再要求密码。

SSH登录认证使用了非对称加密技术。对称加密时通讯双方使用同一个密钥,而非对称加密使用一对密钥:公钥和私钥。这两个密钥配对产生和使用。使用公钥加密的数据,必须用与其对应的私钥才能解开。并且,私钥无法通过公钥获取。因此,公钥是可以被公开的,而私钥则必须安全存放。

SSH 登录后处理

SSH 登录远程主机后,获得一个 login shell。login shell 启动时首先读取全局配置文件 /etc/profile,然后依次查找 ~/.bash_profile~/.bash_login~/.profile 三个用户配置文件,加载第一个找到的并且可读的文件。login shell 退出时读取并执行 ~/.bash_logout 中的命令。

所谓shell,简单说就是命令行界面,让用户可以直接与操作系统对话。用户登录时打开的shell,叫做login shell。用户的账号密码和shell可在创建账户时指定,一般保存在 /etc/passwd 文件中。相关配置文件一般还有 /etc/shadow、/etc/group、/etc/sudoers。本文中的shell是指bash。

Shell 有几种不同的运行模式,login shell 与 non-login shell,交互式 shell 与非交互式 shell。例如:执行 bash --login 启动一个交互式 login shell,执行 bash 启动一个交互式 non-login shell。执行 shell 脚本时启动的是非交互式的 non-login shell。

non-login shell与login shell的主要区别在于它们启动时读取的配置文件不同,从而导致环境不一样。交互式的non-login shell启动时读取~/.bashrc文件。非交互式的non-login shell不读取任何配置文件,而是查找环境变量BASH_ENV,读取并执行BASH_ENV指向的文件中的命令。

~/.bashrc 文件主要保存一些个性化设置,如命令别名、路径等。一般会在 ~/.bash_profile 文件中显式调用。可以使用 source ~/.bashrc. ~/.bashrc 加载修改后的设置。

SSH 配置文件

配置文件通过设置不同的选项来改变程序的运行方式。

服务端配置文件

1
$ sudo vim /etc/ssh/sshd_config
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 常用配置项
Port 12345  # 自定义端口
Protocol 2  # 协议版本 2
MaxAuthTries 3  # 错误尝试次数
StrictModes yes  # 检查相关文件权限
PermitRootLogin no  # 不允许root登录
PermitEmptyPasswords no  # 不允许空密码
RSAAuthentication yes  # RSA认证 (SSH1)
PubkeyAuthentication yes  # 使用公钥认证
AuthorizedKeysFile   .ssh/authorized_keys .ssh/authorized_keys2
PasswordAuthentication yes  # 允许密码认证。公钥认证登录成功后可设为 no
ChallengeResponseAuthentication no  # 不响应身份验证挑战(与前项同为no,禁用密码)
AuthenticationMethods publickey,password  # 可设置公钥密码同时使用
UsePAM no
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 文件目录设置
chmod -R go= ~/.ssh
chown -R $USER:$USER ~/.ssh

# 重载配置文件
sudo systemctl reload sshd # or
sudo systemctl restart ssh # or
sudo service sshd restart

# 设置防火墙
sudo ufw allow 12345
sudo ufw enable
sudo ufw status

客户端配置文件

1
2
3
4
# 系统配置:位于远程主机
$ sudo vim /etc/ssh/ssh_config
# ConnectTimeout 0  # 设置ssh连接超时时间
# StrictHostKeyChecking no  # 跳过 known_hosts 询问
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 用户配置:位于用户目录
$ vim ~/.ssh/config
ForwardAgent yes
ServerAliveInterval 60
StrictHostKeyChecking no

Host example                       # 别名,可代替主机地址
    HostName example.com           # 远程主机IP或地址
    Port 12345                     # 指定端口
    User root                      # 登录用户
    IdentityFile ~/.ssh/id_ecdsa   # 私钥文件

多账户多密钥管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# ~/.ssh/config

Host github
    HostName %h.com
    IdentityFile ~/.ssh/id_ecdsa_github
    User git

Host coding
    HostName git.coding.net
    IdentityFile ~/.ssh/id_rsa_coding
    User git

Host git*
    HostName %h.com
    IdentityFile ~/.ssh/%h_rsa
    User git

Host github.com git.coding.net
    HostName %h
    Port 22
    User git
    IdentityFile  ~/.ssh/id_rsa_git
    IdentitiesOnly yes

Host www
    HostName www.server.com
    Port 22
    User root
    IdentityFile  ~/.ssh/id_rsa
    IdentitiesOnly yes

Host db
    HostName db.example.com
    LocalForward 5433 localhost:5432

Host db2
    HostName x.x.x.x  # IP
    User root
    IdentityFile ~/.ssh/id_ecdsa
    ProxyJump gateway

Host gateway
    HostName proxy.example.com
    User root

使用 ~/.ssh/config 配置文件简化了 SSH 的相关操作,带来了一些有趣的用法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 简化
ssh www  # 登录远程主机
git clone coding:user/test.git

# 远程操作
ssh example "cd /; ls"  # 远程执行命令
ssh example < test.sh  # 远程执行本地脚本
ssh -t example "top"  # 远程执行需要交互的命令
vim scp://www//etc/caddy/Caddyfile  # 远程编辑文件

# 使用远程服务
ssh db
psql -h localhost -p 5433 mydb

# 中转访问远程服务
ssh db2

如果遇到不能将中转服务器当做跳板的情况,检查它的sshd_config文件,修改相应内容如下:

1
2
3
# vim /etc/ssh/sshd_config
AllowTcpForwarding yes
PermitTunnel yes

SSH 命令用法

格式:ssh [params] [flags] [user@]remotehost [command]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 调试信息
$ ssh -vvv host
$ ssh -v user@host

# 测试连接
$ ssh -T git@github.com

# 使用特定私钥
$ ssh -i ~/.ssh/id_rsa_test user@host

# 保持长连接
$ ssh -o ServerAliveInterval=100 user@host

# 跳过 known_hosts 询问
$ ssh -o "StrictHostKeyChecking=no" user@host

# 通过跳板机访问远程服务器
$ ssh -J from@1.2.3.4:22 to@5.6.7.8 -p 22
# 需要分别输入跳板机用户密码、目的服务器用户密码

# ssh user@localhost  # 127.0.0.0/24, ::1

ssh-agent

使用公钥认证的方式可以免密登录,但如果同时有多个远程主机使用了公钥认证,依然需要输入密码,因为ssh客户端不知道要读取哪个私钥去和相应的远程主机上的公钥配对。ssh客户端连接服务端的时候,默认会读取规范私钥文件id_rsa(或其它密钥类型),此外不会自动读取任何一个文件。

1
2
3
# 可以在连接时指定使用哪个私钥去配对:
$ ssh -i ~/.ssh/id_rsa_db user@dbserver
$ ssh -i ~/.ssh/id_rsa_web user@webserver

ssh-agent是一个私钥管理者,一个连接代理。使用ssh-agent后,可以通过ssh-add命令注册本机的私钥。当ssh客户端连接主机时,ssh-agent会自动匹配私钥,无需再指定私钥文件。要使用ssh-agent的转发功能,需要在sshd_config中开启AllowAgentForwarding选项,在ssh_config中开启ForwardAgent选项。

Windows下面 ssh-agent 不自动启动,不长驻内存。

常见问题

Permission denied (publickey)

服务端配置为公钥登录,而客户端没有找到匹配的私钥。

1
2
3
4
5
# 添加私钥到 ssh-agent 缓存
$ ssh-add ~/.ssh/id_rsa_test

# 通过命令行参数使用特定的私钥
ssh -i ~/.ssh/id_rsa_test user@host

Server refused our key

1
2
# /etc/ssh/sshd_config
StrictModes no  # 修改为 no, 默认为 yes

如果StrictModes为yes,必须保证存放公钥的文件夹的拥有者与登陆用户相同。

参考资料

https://www.ssh.com/ \