SSH

SSH 是一种网络协议,用于计算机之间的加密登录。传统的网络服务程序,如:FTP、PoP 和 Telnet 在本质上都是不安全的,因为它们在网络上用明文传送口令和数据,非常容易就可以截获这些口令和数据。

除了可以代替 Telnet 做远程登录外(Telnet 因为采用明文传送报文,安全性不好,很多 Linux 服务器都不开放 Telnet 服务,要想使用 Telnet 需要安装),SSH 还可以为 FTP、PoP、甚至为 PPP 提供一个安全的”通道”(比如 SFTP)。

SSH 存在多种实现,既有商业实现,也有开源实现,其中 OpenSSH 是 Linux 下的开源实现,应用非常广泛。

安装配置

安装

SSH 已经成为 Linux 系统的标准配置,一般 Linux 系统都会自带 SSH(含 SSH Client 和 SSH Server)。

1
2
3
4
5
6
7
8
# 验证 SSH Client
ssh -V # OpenSSH_8.2p1 Ubuntu-4ubuntu0.5, OpenSSL 1.1.1f 31 Mar 2020

# 验证 SSH Server
ps -e | grep ssh

# 安装 SSH(如果返回结果有 sshd,则说明已经安装好 SSH,否则可以使用下面命令在线安装)
sudo apt-get install ssh

注:Windows Git 自带 mingw,mingw 里面有 SSH Client 但是没有 Server,另外,Windows 10 中已默认安装 OpenSSH Client,通过设置 --> 应用 --> 管理可选应用 --> 添加功能,还可以安装 OpenSSH Server。安装目录在 C:\Windows\System32\OpenSSH,里面的程序有有:scp.exe、sftp.exe、ssh.exe…。

另外,Windows 下是通过 net start 和 net stop 来开启和停止某个服务。

1
2
3
# 开启和停止 SSH Server
net start sshd
net stop sshd

配置

对于普通用户,默认配置即可,一般都无需再配置。如果需要配置,/etc/ssh 目录下,ssh_config 是客户端配置文件,sshd_config 是服务端配置文件。修改 sshd_config 文件,这个文件下可以改 SSH 登录端口和禁止 root 登录。改端口可以防止被端口扫描。

1
2
3
4
5
6
7
8
9
# 编辑配置文件
vim /etc/ssh/sshd_config

#Port 22 # SSH 默认端口
#PermitRootLogin yes # 是否允许 root 认证登录
PermitEmptyPasswords no # 是否允许空密码登录
#PasswordAuthentication yes # 是允许密码认证
#AuthorizedKeysFile .ssh/authorized_keys # 默认公钥存放的位置
...

修改完成后,重启 sshd 服务

1
service sshd restart

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 登录 SSH Server(更详细的可以用 ssh -h 查看)
ssh [-l login_name] [-p port] [user@]hostname

# 不指定用户(默认使用 root 账户登录)
ssh 192.168.0.11

# 指定用户
ssh -l root 192.168.0.11
ssh root@192.168.0.11

# 指定 SSH 端口(SSH 默认端口是 22)
ssh -p 12333 192.168.0.11
ssh -l root -p 12333 192.168.0.11
ssh -p 12333 root@192.168.0.11
1
2
3
4
5
6
7
8
# 生成密钥
ssh-keygen -t rsa -C "any comment can be here" # -t 指定密钥的类型(rsa、dsa...),-C 指定识别这个密钥的注释(可选)

# 将本地的公钥追加至远程主机的 authorized_keys 文件内
ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.10.141

# 验证公钥登录是否设置成功
ssh -T root@192.168.10.141

生成密钥时,会提示设置私钥的密码 Enter passphrase (empty for no passphrase),直接回车则为不设密码,如果设置了私钥密码,每次使用 SSH 进行操作都需要输入私钥密码。虽然这样增加了安全性,但是使用 SSH 的目的很多时候就是想更加方便,跳过输入账号密码这一步。只要保存好自己的私钥不泄露,一般不设私钥密码是没有太大的风险的。如果已经生成带密码的私钥,又想取消密码可执行以下操作。

1
2
# 修改私钥密码
ssh-keygen -f id_rsa -p # 要在 ~/.ssh 目录下执行

注:作为 SSH 客户端,~/.ssh 目录下,会有 3 个文件 id_rsa、id_rsa.pub、known_hosts,作为 SSH 服务端,也会有三个文件 id_rsa、id_rsa.pub、authorized_keys。

1
2
3
4
id_rsa              # 保存的是私钥
id_rsa.pub # 保存的是公钥
known_hosts # 保存的是 SSH 服务端公钥指纹
authorized_keys # 保存的是 SSH 客户端公钥

口令登录

1
2
3
4
5
# 基本格式
ssh username@host

# 以远程 192.168.10.141 这台机器为例
ssh root@192.168.10.141

输入密码后回车,登录成功。在使用完毕后,通过 exit 命令退出 ssh 登录

中间人攻击

SSH 之所以能够保证安全,原因在于它采用了公钥加密。整个过程是这样的:

1
2
3
1)远程主机收到用户的登录请求,把自己的公钥发给用户;
2)用户使用这个公钥,将登录密码加密后,发送回来;
3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录;

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

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

为了解决这个问题,SSH 在首次登录时会让用户确认是否相信远程主机的,确信过的远程主机会被保存在 known_hosts 这个文件中。

  • known_hosts

第一次登录远程主机时,会出现下面这样的提示(意思是,无法确认 192.168.10.14 主机的真实性,只知道它的公钥指纹,问你还想继续连接吗?):

1
2
3
The authenticity of host '192.168.10.14 (192.168.10.14)' can't be established.
RSA key fingerprint is SHA256:4SsJ0OdMFdSGTaCJDYph5J3LtQF2fFC3MTZ1bwr8G7g.
Are you sure you want to continue connecting (yes/no)?

注:所谓”公钥指纹”,是指公钥的 SHA256 或 MD5 计算结果。因为公钥长度较长(这里采用 RSA 算法,长达 1024 位),很难比对,所以对其进行 SHA256 或 MD5 计算,将它变成一个 128 位的指纹,再进行比较,就容易多了。

很自然的一个问题就是,用户怎么知道远程主机的公钥指纹应该是多少?回答是没有好办法,远程主机必须在自己的网站上贴出公钥指纹,以便用户自行核对。经过风险衡量以后,用户决定接受这个远程主机的公钥:

1
Are you sure you want to continue connecting (yes/no)? 

系统会出现一句提示,表示 host 主机已经得到认可:

1
Warning: Permanently added '192.168.10.141' (ECDSA) to the list of known hosts.

当远程主机的公钥被接受以后,它就会被保存在文件 $HOME/.ssh/known_hosts 之中。下次再连接这台主机,系统就会认出它的公钥已经保存在本地了,从而跳过警告部分,直接提示输入密码。

每个 SSH 用户都有自己的 known_hosts 文件,此外系统也有一个这样的文件,通常是 /etc/ssh/ssh_known_hosts,保存一些对所有用户都可信赖的远程主机的公钥。

公钥登录

使用密码登录,每次都必须输入密码,非常麻烦。好在 SSH 还提供了公钥登录,可以省去输入密码的步骤。

所谓”公钥登录”,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录 shell,不再要求密码。

这种方法要求用户必须提供自己的公钥。如果没有现成的,可以直接用 ssh-keygen 生成一个。

操作步骤

  • 本机生成密钥文件

本地系统执行以下命令,一路回车,生成密钥文件(以 DSA 算法生成公钥和私钥),其中有一个问题是,要不要对私钥设置口令(passphrase),如果担心私钥的安全,这里可以设置一个。

1
ssh-keygen -t rsa

注:完成后,控制台里面有密钥生成的位置信息,生成密钥文件里面,$HOME/.ssh/ 目录下,会生成两个文件,id_rsa 为私钥文件,id_rsa.pub 为公钥文件。

  • 将公钥文件传输到远程机器

本地机器执行以下命令,将公钥文件传输的远程机器。

1
2
3
4
5
6
7
8
9
10
# 方法一:在本机上执行 ssh-copy-id 命令,ssh-copy-id 是一个快捷命令,用于将本地的公钥追加至远程主机的 ~/.ssh/authorized_keys 文件内
ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.10.141

# 方法二:也可以通过其他手段来完成,比如 cat、scp、touch 组合命令,比如:
# 步骤 1. 在本机上执行
ssh root@192.168.10.141 "mkdir .ssh" # 需要输入密码
scp ~/.ssh/id_rsa.pub root@192.168.60.141:.ssh/id_rsa.pub # 需要输入密码
# 步骤 2. 在远程主机上执行
touch ~/.ssh/authorized_keys # 如果已经存在这个文件, 跳过这条
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys # 将 id_rsa.pub 的内容追加到 authorized_keys 中
  • 登录

再次使用已经做免密处理的用户登录远程机器,已经不需要密码了,免密登录处理完成。

1
ssh root@192.168.10.141

authorized_keys

远程主机将用户的公钥,保存在登录后的用户主目录的 $HOME/.ssh/authorized_keys 文件中。公钥就是一段字符串,只要把它追加在 authorized_keys 文件的末尾就行了。

这里不使用上面的 ssh-copy-id 命令,改用下面的命令,解释公钥的保存过程:

1
ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub

这条命令由多个语句组成,依次分解开来看:

1
2
3
4
ssh user@host                                      # 表示登录远程主机
'mkdir .ssh && cat >> .ssh/authorized_keys' # 表示登录后在远程 shell 上执行的命令
mkdir -p .ssh # 这个命令的作用是,如果用户主目录中的 .ssh 目录不存在,就创建一个
cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub # 这个命令的作用是,将本地的公钥文件 ~/.ssh/id_rsa.pub,重定向追加到远程文件 authorized_keys 的末尾

写入 authorized_keys 文件后,公钥登录的设置就完成了。

文件和目录权限

配置完成后,如果发现还是不能免密登录,查看日志发现,这是权限问题引起的:

1
tail /var/log/secure -n 20

Authentication refused: bad ownership or modes for directory /root,从字面上可以看出是目录的属主和权限配置不当,查找资料得知:SSH 不希望 home 目录和 ~/.ssh 目录对组有写权限。

修改远程机器的 .ssh 目录需要 700 权限,authorized_keys 文件需要 600 权限即可:

1
2
3
chmod 755 ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

注:关于 Linux 权限,请查看 Linux 相关章节。