VPS 初体验(二)使用 SSH 连接远程主机

发起连接

Linux 发行版 和最新的 Windows 10 都自带了 OpenSSH 客户端,以 Windows 10为例,OpenSSH 客户端程序位于C:\Windows\System32\OpenSSH\ssh.exe 这个路径下,该路径已经被添加到系统环境变量 PATH 中,因此在终端中可以直接使用 ssh 命令来运行 SSH 客户端。在 ssh 命令后面接 -V 参数来查看 SSH 客户端版本号。

1
ssh -V

SSH 客户端最常用的功能就是用来登录远程的服务器。

1
ssh hostname

hostname 用来指定主机名,可以是域名、主机的 IP 地址、也可以是配置文件中的主机别名。默认使用当前客户端的用户名(也就是当前 Windows 所使用的的用户名)去登录远程主机。

可以在主机名前面用指定要登录的用户名,用 @ 符号分隔。

1
ssh username@hostname

也可以使用 -l 参数来指定要登录的用户名,和上面等效。

1
ssh hostname -l username 

SSH 客户端默认连接服务器的 22 端口(SSH 服务端程序的默认监听端口),-p 参数可以指定其他端口。

1
ssh hostname -p 2222

当客户端向服务端发起 SSH 连接请求时,SSH 服务端会将 /etc/ssh 目录下的 Host Key 公钥发回客户端,用来认证服务端的身份信息。

如果用户是第一次连接到该主机,SSH 客户端会提示用户确认服务端的公钥指纹。

如果选择信任该公钥,会将主机名和对应的公钥都储存在本机的 ~/.ssh/known_hosts 文件中。以后再连接该主机时,如果主机发回的公钥和文件中的不符,则提示可能遭受中间人攻击并断开连接(HTTPS 用 CA 证书中心解决了该问题)。

如果确认是远程主机因为某些原因,例如重装系统或者重装 SSH 服务而重新生成了 Host Key公钥,那就需要在本地主机上使用以下命令将原来的公钥信息删除,或者手动打开 ~/.ssh/known_hosts 文件删除对应的项。

1
ssh-keygen -R hostname 	# hostname 是发生公钥变更的主机名。

删除了原来的公钥信息后,重新执行 ssh 命令连接远程服务器,重新确认服务端的公钥指纹,就可以发起连接请求了。

发起连接请求后 SSH 客户端会和服务端是进行版本协商、算法协商、秘钥交换,其中又用到了对称加密、非对称加密和密钥交换的各种算法(思路有和 HTTPS 中的 SSL/TLS 协议类似的地方,都是用非对称加密来交换对称秘钥,真正的消息内容都是使用的都是基于对称加密的的临时回话密钥(Session Key)进行加密,如果全程都使用非对称加密效率太低)。只需要知道 SSH 连接成功后建立了安全通道,具体的连接过程在此不深究。

客户端就与服务器建立连接 SSH 连接成功后,ssh 就会要求用户输入所要登录账户的密码,输入用户密码验证正确以后,就可以得到远程服务器的 Shell 环境了。

公钥登录

原理步骤

公钥免密登录步骤是:

  1. 用户根据非对称加密算法,生成一对密钥对。
  2. 提前将密钥对中的公钥放置在远程主机用户目录下的 ~/.ssh/authorized_keys 文件中。
  3. SSH 客户端指定某用户发起登录请求。
  4. SSH 服务端收到该用户的登录请求,SSH 服务端生成一个随机数,使用指定用户的用户目录下的 ~/.ssh/authorized_keys 文件中的公钥列表中一个公钥加密,将加密后的数据给客户端。
  5. 客户端使用密钥对中的私钥对这段随机数解密,解密结果和 Session ID 使用 MD5 算法生成摘要发回给服务端。如果私钥解密的数据发回服务器的时候如果被中间人劫持因为 Session ID 也可以
  6. SSH 服务端用之前生成的随机数同样加上 Session ID 计算 MD5 hash 值,如果和客户端发过来的摘要匹配成功,就说明请求者拥有公钥所对应的私钥,即可以以该用户登录,不再需要输入密码。如果不匹配则回到第 4 步中使用公钥文件中的剩余公钥重复这个过程。如果所有的公钥都失败,则说明公钥登录失败,会提示用户输入密码登录。
  7. SSH 服务端遍历登录请求中指定用户的用户目录下的 ~/.ssh/authorized_keys 文件中的公钥来解密,如果有一条公钥的解密结果和服务端之前发出的数据一致,如果该文件中的所有公钥的解密结果都匹配不上则显示输入密码登录。

(其实还有 Session ID 等其他信息)数字签名,然后发回给服务端。

关于第 4 步中发送的随机数到底是直接发送还是使用 authorized_keys 中的公钥将加密后再发送的有不少的争议。

阮一峰的文章说的直接发送,但是要在第 5 步对响应给服务端的数据进行数字签名,如果第 5 步服务端使用公钥解签得到的摘要和原本的摘要一致也能证明认证用户的身份。

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

个人觉得阮提出的方法效率会更高,因为 ~/.ssh/authorized_keys 文件中可以存放多个公钥,每一行都是一个公钥,服务端并不知道应该用哪个公钥来加密随机数,最差结果就是所有公钥都要按照上面的流程尝试一边,效率显然不如直接发送然后在服务端遍历公钥列表来解密私钥签名信息对比效率高。并且哪怕是随机数被中间人偷听了也无所谓,中间人没有真正的私钥能对随机数据进行正确签名的。

具体是哪一种也没去看底层代码验证过,无论是公钥加密,私钥解密,还是私钥签名,公钥解签,都能证明你是私钥的拥有者即可。

虽然个人很信任阮大,但是因为持后一种说法人太多,并且差了不少英文文章都是这样说,还是采纳了后一种说法。

注意:很多人的文章说用户在发起登录请求会把公钥发送给服务端,这样服务端就知道用哪个公钥加密了,但是经过实验在本地主机删除了公钥文件也可以成功登录,说明并不存在发送公钥的行为。

生成密钥对

首先要使用 ssh-keygen 命令,生成一对密钥对。-t 是用来指定非对称加密使用的算法,可选 dsa 或者 rsa,默认值是 rsa。-b 是用来指定秘钥的二进制位数,-b 至少应该是 1024,更安全一些可以设为 2048 或者更高。-b 至少应该是 1024,更安全一些可以设为 2048 或者更高。-C 参数可以为密钥文件指定新的注释,建议的格式为 username@hostname-f参数指定生成的秘钥文件路径和名称。这些参数包括注释都是可选的,如果省略就使用默认值。

注意:如果之前使用过 Github 的 SSH 免密登录功能,该目录下会有生成过的秘钥对,可以直接使用这套秘钥对来进行免密登录。当然也可以再创建新的密钥对,但是注意用 -f 参数指定秘钥文件路径和名称,不要把原来的秘钥文件覆盖了,否则 GitHub 的免密登录会失败。

输入下面的命令就会在 SSH 的配置目录 ~/.ssh 下生成名为 id_rsa(私钥)和id_rsa.pub(公钥)秘钥对文件。

1
ssh-keygen -t rsa -b 4096 -C "root@kiku.vip"

在生成秘钥的过程中会询问是否要为私钥设定密码(passphrase,为了和用户密码 password 区别),如果设定密码则在使用私钥前还要输入密码对私钥解锁,虽然和密码口令登录一样时要输入密码,但是更加安全。如果为了方便,不想设定密码,直接输入两次回车就好。

上传公钥

免密登录能成功的前提是用户已经将提前将秘钥对中的公钥添加到远程主机的 ~/.ssh/authorized_keys 文件中,因此生成密钥对以后,还得把公钥必须上传到服务器,才能使用公钥登录。

要以哪个用户的身份登录到服务器,密钥就必须保存在该用户主目录的~/.ssh/authorized_keys文件。

下面提供三种上传公钥到远程主机上的方法:

  1. 如果购买的是大厂云服务器,可以直接在控制台中上传公钥。以腾讯轻量应用服务器为例:
    首先要在控制台中创建秘钥:
    image-20211014162335502
    image-20211014162545603
    然后将秘钥绑定到我们远程主机上。
    注意:腾讯云控制台上传的公钥只能绑定在 root 用户下,并且绑定后将不能再通过密码方式 SSH 登录。
    image-20211015175759714

  2. 手动将本地的公钥文件写入到远程主机上。
    在远程主机执行下面的命令。

    1
    2
    3
    4
    mkdir ~/.ssh  
    chmod 700 ~/.ssh
    vi ~/.ssh/authorized_keys
    chmod 600 ~/.ssh/authorized_keys

    第 1、2 行是在用户的主目录下创建 .ssh 的目录并设置权限为只有该用户可以访问,第 3 行打开信任的公钥文件,将公钥粘贴进去然后保存退出,第 4 行更改公钥文件的权限只有所有者有读写权限。如果权限设置不对,SSH 服务器可能会拒绝读取该文件。

    注意:该文件中可以存放多个公钥,每一行都是一个公钥,为了不影响其他客户端的登录应该把公钥追加到末尾,而不是直接覆盖。并且最后在后面添加一个换行,防止下次添加的公钥接在了同一行后面。

    也可以使用在本地主机运行下面的命令将公钥上传到远程主机,其中~/.ssh/id_rsa.pub 替换成本地公钥存放的位置, username@hostname 要替换成你所要登录的用户名和主机名。

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

    但是还是要登录远程主机,执行下面的命令修改目录和文件的权限才能成功。

    1
    2
    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/authorized_keys
  3. 使用 ssh-copy-id 命令:自动上传公钥,Linux 上自带的 OpenSSH 自带一个 ssh-copy-id 命令,可以自动将公钥拷贝到远程服务器的~/.ssh/authorized_keys文件。
    用户在本地 Linux 计算机执行下面的命令,就可以把本地的公钥拷贝到服务器。 -i 参数指定上传的公钥文件。

    1
    $ ssh-copy-id -i key_file user@host

    注意:Windows 10 的自带的 OpenSSH 并没有 ssh-copy-id 工具,因此无法使用该命令,网上有一些第三方对的实现,可用性没有测试过,建议 Windows 使用前面两种方法,

上传成功后使用下面命令就可以顺利免密登录,不用再输入密码。可以使用 -i 参数指定使用的私钥文件,默认使用的私钥文件为 ~/.ssh/id_rsa

1
ssh admin@kiku.vip -p 2222 -i ~/.ssh/id_rsa

配置文件

SSH 客户端的相关配置文件在本地主机的 ~/.ssh 目录下,使用 ssh-keygen 命令生成的密钥对,还有信任主机列表 known_hosts 文件都在该目录下。

~/.ssh/config 文件是 SSH 客户端配置文件,如果该目录下不存在该文件,可以手动创建一个名为 config 无后缀格式的纯文本文件。

使用 SSH 客户端配置文件,可以按照不同服务器,列出各自的连接参数,从而不必每一次登录都输入重复的参数。下面是一个例子,例子里面的缩进格式并不是必需的,只是为了视觉上区分不同主机的设置。

1
2
3
4
5
6
7
8
9
10
11
12
Host *
Port 2222
# 断开时重试连接的次数
ServerAliveCountMax 5
# 每隔 60 秒自动发送一个 keepalive 信号以保持连接
ServerAliveInterval 60

Host bloghost
HostName kiku.vip
User root
Port 2222
IdentityFile ~/.ssh/bloghost

Host *表示对所有主机生效,后面的 Port 2222 表示以后连接所有服务端都使用 2222 端口而不是默认的 22 端口,这样就不用在每次登录时都使用 -p 特别指定端口了。

SSH 客户端连接服务端长时间不操作会终止回话断开连接,下次连接又要发起登录请求,后面的两行可以保持长时间连接。

Host bloghost 表示新的一项主机设置,真正连接主机名由下面的 HostName 决定,bloghost 可以理解为 kiku.vip 这个主机名的别名,如果使用 ssh 连接的是配置文件中的别名,就会套用后面指定的参数。例如 ssh bloghost 就相当于下面的命令:

1
ssh kiku@kiku.vip -p 2222 -i ~/.ssh/id_rsa

参考

  1. 阮一峰:SSH原理与运用(一):远程登录

  2. 网道:SSH 教程

  3. B 站:2.1.2 SSH工作原理

  4. ssh基础使用

  5. 图解 SSH 原理

  6. 每天都在用 SSH,你知道 SSH 的原理吗?

  7. rfc4252:Public Key Authentication Method: “publickey”

  8. How Key Challenges Work