详细记录一下关于ssh的内容,这部分内容比较繁杂。

基本使用(1)

ssh 最基本的用途就是登录远程服务器

1
ssh user@hostname

其中:

  • user是登录用户名
  • hostname是主机名,它可以是域名,也可以是某个具体的 IP 地址或局域网内部的主机名。

可以缺省用户名,此时将使用本地用户名作为远程服务器的登录用户名。

1
ssh hostname

用户名也可以通过-l参数指定,这样用户名和主机名就不用写在一起了,在脚本中可能更方便

1
ssh -l username hostname

ssh 会默认连接远程服务器的22端口,使用-p参数也可以指定其他端口(同时也要修改远程服务器的监听端口)

1
ssh -p 8821 foo.com

ssh 在连接到远程服务器后会进行验证:如果第一次通过ssh连接某一台服务器, 命令行会显示一段文字,表示不认识这台机器,提醒用户确认是否需要连接

1
2
3
The authenticity of host 'foo.com (192.168.121.111)' can't be established.
ECDSA key fingerprint is SHA256:Vybt22mVXuNuB5unE++yowF7lgA/9/2bLSiO3qmYWBY.
Are you sure you want to continue connecting (yes/no)?

这段文字告诉用户,foo.com这台服务器的指纹是陌生的,用户需要确认是否要继续连接(输入 yes 或 no)。

所谓“服务器指纹”指的就是 SSH 服务器提供的公钥的哈希值,利用公钥的特性可以识别和确认服务器,下面的命令可以查看某个公钥所对应的指纹

1
2
ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key.pub
256 da:24:43:0b:2e:c1:3f:a1:84:13:92:01:52:b4:84:ff (ECDSA)

ssh 会自动将曾经连接过的所有服务器公钥的指纹,储存在~/.ssh/known_hosts文件中,具体的内容格式对Windows和Linux不太一样。 每次连接服务器时,通过该文件判断是否为陌生主机(陌生公钥),对于陌生主机需要手动确认,对于已经存储在~/.ssh/known_hosts文件中的则不再需要确认。

我们可以使用下面的命令检查指定的主机名是否在known_hosts中,或者将指定主机名从known_hosts中移除

1
2
3
4
5
# find
ssh-keygen -F example.com

# remove
ssh-keygen -R example.com

ssh在验证了服务器指纹之后会要求用户输入所要登录账户的密码,用户输入并验证密码正确以后,就可以登录远程服务器的 Shell 了。 (如果采用下面的密钥登陆,则无需输入密码)

有时我们只需要临时登录并执行一条简单命令,可以把命令直接加在后面,在登录之后会自动执行这条命令并输出到本地,然后自动退出ssh登录,例如

1
ssh user@hostname cat /etc/hosts

密钥

非对称加密算法

ssh支持很多种非对称加密算法,用于登陆时的认证签名:

  • RSA算法,基于大数分解,兼容性最好,所有SSH客户端都支持此算法。
  • ECDSA算法:基于椭圆曲线,需要在新的场景中使用更安全的密钥时,可以选择此类型。
  • ED25519算法:基于Edwards曲线(也属于椭圆曲线),提供极高的安全性和性能,兼容性相对最差。

除此之外,还有两种已经被淘汰的算法:SSH协议版本1的RSA算法(目前使用的是版本2的RSA算法)和DSA算法(基于离散对数),不建议在新场景中使用。

关于密钥的位数:

  • RSA算法:必须加大密钥位数来提升安全性,建议密钥位数至少为 2048 位,推荐使用 3072 位或 4096 位以获得更高的安全性。
  • ECDSA算法:只支持三种密钥大小:256,384和521位,即使最低的 256 位也已经非常安全,384 位和 521 位提供额外的安全冗余。
  • ED25519算法:密钥大小固定为256 位,安全性在 RSA 2048 与 RSA 4096 之间,且性能数十倍于 RSA 算法。

有的比较激进的组织出于安全性的原因,已经考虑禁止使用RSA密钥,建议使用后两种算法,并且对于ECDSA算法建议使用最长的512位。

密钥文件

密钥主要是用来签名验证自身的身份,因此对于一个固定PC/服务器的一个用户,通常只需要在本地维护一个密钥对, 用户必须保证安全地保存密钥,并且需要将公钥手动传递给希望远程登陆的服务器,见下文。 密钥通常存放在~/.ssh目录下,下面是这些文件的默认名称

  • id_rsaid_rsa.pub:用于 SSH 协议版本2 的 RSA 私钥和公钥。
  • id_ecdsaid_ecdsa.pub:ECDSA 私钥和公钥。
  • id_ed25519id_ed25519.pub:ED25519 私钥和公钥。
  • identityidentity.pub:用于 SSH 协议版本1 的 RSA 私钥和公钥。(通常没有,除非在很老的机器中)

在Linux系统中,ssh要求用户的私钥文件不能被其它用户直接访问到,否则会拒绝使用这个不安全的私钥,可以使用chmod命令将私钥文件的权限设置为600或400,对于公钥文件则没什么要求,例如设置为644即可。

私钥文件和公钥文件都是文本文件,私钥例如

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
-----BEGIN OPENSSH PRIVATE KEY-----
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
yf/PzW634X+YF3AAAACWxpc2h1QFRYSgE=
-----END OPENSSH PRIVATE KEY-----

RSA算法需要足够长的私钥来保证安全性,如果采用新的算法(ECDSA或ED25519)则私钥长度可以短很多。

公钥例如

1
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCmP1rbqoMBBO9rBNSfRYZyyRUlWeF1pldu3bW8MMfXf/oR78CEN4MT643ZV994qlukZJHjja3cE9X23mUley78DMUaSCIDF+KuBlZYNv1Iaok6MMbUMNfXXESms5bjAT76kpx35IGvbSyOnpgmXMeMcxGbRtgbe448mZCESh8/+mUciznqZiHjhC8oZWJOrvJyx/TYR/wfoNz1MxfafBf0HEUSShxU6HjfQHlt5+rA++mzjOtlTEcqxWIXKDvDy3iVn885kwpUQazQ8D2rJZBmycggRI4TGw6iZYyp5YZ9akXnW2qmT71GNxI6Xt0SUOQIlHHD/9U1yZhebAbkCaPzHbiG1XII78PVxOUcSyo7cp5fVmINwgjEuq0oCbwSdfGPISl32NKeBzGDSLz/qwb9YQXGZRv5FDWP7TZkO78r8+RGKDDWHP8F7XuE3sPrObkCTkMim1jxWdIdXBs/JbG/MJQaplseUKGTKlmeJ5x9SvsmlaE09i2ST7dIAnlrtK0= your_email@domain.com

公钥内容只有一行,比私钥长度小很多,在公钥文件的开头会表明当前算法的类型,在文件末尾的空格之后会有一个注释,标记公钥所对应的用户名和主机名,这个注释并不是密钥的一部分,只是便于区分不同的公钥,我们可以直接修改注释,这不会对密钥对产生影响。

密钥生成

ssh 提供的配套工具 ssh-keygen 可以用于生成密钥(默认使用RSA算法),当然我们也可以通过其它工具生成密钥对,因为算法都是公开的

1
ssh-keygen

ssh-keygen 需要我们确认或修改几个信息:

  1. 保存的路径和文件名,使用默认的路径和文件名即可,还可以通过-f选项指定,见下文
  2. 密钥对的保护密码(passphrase),客户端在每次使用这个密钥对时需要用户输入保护密码,通常使用空密码即可,还可以使用-N选项指定保护密码,见下文

最后 ssh-keygen 会生成一对密钥对,将其存储到指定位置,然后显示公钥的指纹和一个基于公钥生成的图像, 用图像来判断比使用字符串形式的指纹对人类更加友好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
The key fingerprint is:
SHA256:MaA3rGf/h3Jh5jVehAw6QerUG0TMOTJN/n93P2zwMXs xxx@yyy
The key's randomart image is:
+--[ED25519 256]--+
| .B=. |
| oo*B . |
| . *o*+ o . |
| = .o* o . |
| . + S.. . |
| o . +.o..o |
| .+ =.o= *|
| ..+ o. BE|
| o.. . +|
+----[SHA256]-----+

ssh-keygen支持很多选项,下面介绍几个最常用的选项。

-t选项可以指定加密算法,默认是RSA算法

1
ssh-keygen -t rsa

-b选项可以指定密钥的二进制位数

1
ssh-keygen -t rsa -b 4096

对于位数可变的算法,我们必须使用合法的位数,RSA算法默认采用2048位,ECDSA算法默认采用256位,位数都是足够的;对于位数固定的ED25519算法,这个选项会被直接忽略。

-C选项可以为公钥文件指定注释,格式为username@host,默认是当前用户名和当前主机名

1
ssh-keygen -C "your_email@domain.com"

-f参数可以指定生成的私钥文件

1
ssh-keygen -f mykey

这个命令会在当前目录生成私钥文件mykey和公钥文件mykey.pub

-N参数可以指定私钥的保护密码(passphrase)

1
2
3
ssh-keygen -N secretword
# empty
ssh-keygen -N ''

使用下面的命令可以利用现有的私钥生成对应的公钥

1
2
ssh-keygen -y -f key_file_name # 输出到标准输出流
ssh-keygen -y -f key_file_name > key_file_name.pub

完整的生成密钥对的命令如下

1
ssh-keygen -t rsa -C "your_email@domain.com" -f key_file_name -N ''

基本使用(2)

密码登陆过于繁琐且不够安全,更建议的做法是使用密钥登陆,大致流程如下:

  • 预备步骤,客户端通过ssh-keygen生成自己的公钥和私钥,手动将客户端的公钥放入远程服务器中的指定位置。
  • 第一步,客户端向服务器发起 SSH 登录的请求。
  • 第二步,服务器收到用户 SSH 登录的请求,发送一些随机数据给用户,要求用户使用私钥加密以证明自己的身份。
  • 第三步,客户端收到服务器发来的数据,使用私钥对数据进行加密,然后再发还给服务器。(使用私钥加密数据的过程通常被称为签名)
  • 第四步,服务器收到客户端发来的加密签名后,使用对应的公钥解密检查,如果数据一致就允许用户登录。

密钥对的生成在上面已经介绍,下面关注将公钥上传到服务器中。

用户公钥保存在服务器的~/.ssh/authorized_keys文件中,把公钥添加到这个文件之中就是把公钥上传到服务器了。 authorized_keys是纯文本文件,其中可以包括多个公钥,每个公钥占据一行,把最新的公钥粘贴到文件尾部即可。 如果authorized_keys文件不存在,也可以直接手动创建。

和私钥文件类似,ssh对authorized_keys文件的权限也有要求,建议设置为644,权限不对可能导致ssh使用失败。

用户可以使用密码登陆到远程服务器,然后手动编辑该文件,把公钥粘贴进去,也可以直接在本机计算机上,执行下面的命令

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

注意将这里的文件替换为需要上传的密钥文件,需要确保服务器上的authorized_keys没有格式错误,并且文件权限正确。

在配置了密钥对之后,我们就可以使用-i选项指定密钥而非使用密码进行远程登陆

1
ssh -i /path/to/private_key user@hostname

如果缺省-i选项,ssh客户端会依次尝试使用下面的几个私钥文件(如果存在的话)

  • ~/.ssh/id_rsa
  • ~/.ssh/id_dsa
  • ~/.ssh/id_ecdsa
  • ~/.ssh/id_ed25519

我们还可以使用~/.ssh/config文件进行更详细的配置,见下文。

配置

客户端配置

ssh客户端通常需要关注如下两个配置文件:

  • ~/.ssh/known_hosts:记录登陆过的服务器的公钥指纹,还有一个known_hosts.old是在主机指纹发生更改时,客户端进行的自动备份;
  • ~/.ssh/config:用户级配置文件,与之相对的是全局配置文件/etc/ssh/ssh_config,用户级的优先级更高。

config配置文件由一系列 Host 块组成,每个块包含一组针对特定主机或一组主机的配置选项。 配置冲突时,后面的块优先级比前面的更高,通常会先将通用的放在前面,具体的放在后面,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 通配符配置
Host *
ServerAliveInterval 60

Host *.example.com
User commonuser
Port 2222

# 具体主机配置,优先级更高

Host myserver1.example.com
User user1
Port 2200

Host myserver2
HostName myserver2.example.com
User user2
Port 2222

Host myserver3
HostName myserver3.example.com
User user3
Port 2200
IdentityFile ~/.ssh/id_rsa3

解释一下这里的选项:

  • Host <pattern>:指定主机模式,可以是ip或具体主机名,也可以包含通配符
  • HostName:实际要连接的主机名
  • User:登录用户名
  • Port:端口号(默认是22)。
  • IdentityFile:用于身份验证的私钥文件路径,如果缺省则会依次尝试id_rsaid_dsaid_ecdsaid_ed25519这些默认文件。
  • ServerAliveInterval:发送到服务器的间隔秒数,以保持连接活跃
  • ServerAliveCountMax:在放弃连接之前,服务器没有响应的最大次数

服务端配置

ssh服务端通常需要关注如下两个配置文件:

  • ~/.ssh/authorized_keys:记录允许从远程登陆的用户公钥;
  • /etc/ssh/sshd_config:ssh 服务端(sshd服务)的全局配置文件。

修改服务器上的/etc/ssh/sshd_config可以控制用户的ssh登陆行为,例如

1
2
3
4
5
6
7
8
9
10
11
# 设置ssh的端口(还需要修改防火墙等)
# 默认:22
Port 2222

# 不允许使用密码登陆
# 默认:yes
PasswordAuthentication no

# 不允许root用户从远程登陆(建议先用普通用户登陆,然后切换为root用户)
# 默认:不允许root用户远程密码登陆 prohibit-password
PermitRootLogin no

修改需要重启sshd服务才能生效。

除了配置文件,ssh服务端在/etc/ssh/目录中还存储了所有的服务端密钥对,例如/etc/ssh/ssh_host_ecdsa_key, 这些服务端密钥对被用于生成服务器指纹,重装ssh服务端会导致这些密钥对重新生成,之前的指纹记录就全部失效,我们必须在重装之前备份这些密钥。

git是一个特殊的用户,我们需要避免使用git用户登陆到远程服务器的shell,通常的做法是修改/etc/passwd文件中git所在的行,使用定制的git-shell替代默认的bash

1
git:x:1003:1003:,,,:/home/git:/usr/bin/git-shell

当然我们也可以直接在/etc/ssh/sshd_config中通过白名单的形式禁止git用户的远程登陆, 但这会导致我们无法通过ssh访问在服务器中的git仓库。

文件权限小结

整理一下关于Linux系统中.ssh/目录下的权限要求:

  • 私钥:自动生成的私钥为600,改成400也可以,如果对其它用户可读就会报错
  • 其它所有的文件都建议使用644权限,包括:
    • 公钥
    • authorized_keys
    • config
    • known_hosts

进阶使用

端口常识

下面是不同端口号段的含义:

  • 0-1023: 知名端口,预留给常见的服务和协议。
  • 1024-49151: 注册端口,一般分配给用户进程或特定服务。
  • 49152-65535: 动态端口或私有端口,通常用于临时目的,如客户端程序向服务器发起连接。

知名端口例如:

  • HTTP:80
  • HTTPS:443
  • FTP:21
  • SSH:22
  • DNS:53

可以使用下面的命令查看当前是否监听了某个端口

1
2
# Windows
netstat -ano | findstr :22022

如果想要关闭监听,可以在任务管理器搜索进程号,然后结束任务即可。如果要终止ssh对端口的监听,也可以直接搜索ssh任务,然后关闭。

端口转发

ssh 除了登录服务器,还有一大用途:作为加密通信的中介,充当服务器之间的通信加密跳板。这个功能称为端口转发(port forwarding),又称 ssh 隧道(tunnel)。端口转发有两个主要目的:

  • 将不加密的数据放在 ssh 安全连接里面传输,使得原本不安全的网络服务增加安全性。
  • 作为数据通信的加密跳板,绕过网络防火墙。

ssh 提供三种形式的端口转发:

  • 本地转发(-L选项)
  • 远程转发(-R选项)
  • 动态转发(-D选项)

在下文中还可以加上:

  • -N选项,它表示这个 ssh 连接只用于端口转发,不会登录远程 Shell 并执行远程命令,只能充当隧道。
  • -f选项,它表示ssh 连接在后台进行。

这些选项的位置建议放在最后,至少要在转发的选项和参数之后。实践发现:

  • 如果两个选项都不加,那么端口转发会打开一个远程shell,并且shell退出后隧道自动关闭;
  • 如果加上-N选项,那么不会打开远程shell,但是在当前shell被关闭后,隧道也会自动关闭;
  • 如果加上-N -f选项,那么不会打开远程shell,并且在当前shell退出后,隧道仍然存在,需要找到后台进程然后手动关闭;
  • 只加上-f选项会报错。

下面的讨论都不再加上这些选项。

本地转发

本地转发是在本地计算机建立的转发规则,具体含义为: 创建一个本地端口,将发往该端口的所有通信都通过 ssh 服务器,转发到指定的远程服务器的端口。 这种情况下,ssh 服务器只是一个作为跳板的中介,用于连接本地计算机无法直接连接的远程服务器。

1
2
3
4
5
6
7
场景:
A->C,某服务不可直连,C处于内网中被保护,A处于外网,无法连接到C
A->B,SSH可直连,B也在内网中,但是可以被外网的A访问到
B->C,某服务可直连,B和C同在内网中,相互可以正常通讯、

实现:(本地转发,在A上执行命令)
A->B->C,利用B作为跳板,实现A对C的访问

本地转发的命令格式为(在A上执行命令)

1
ssh -L A-tunnel-port:C-host:C-port B-host

其中:

  • A-tunnel-port:主机A上的端口,作为隧道入口
  • C-host:主机C,可以是C的内网ip,只要B可以访问即可
  • C-port:主机C的目标端口,如果需要ssh访问主机C,那么使用22号端口
  • B-host:跳板机B

例如(在A上执行命令)

1
ssh -L 22022:xxx.xxx.xxx.xxx:22 user@remotehost_B

在建立了ssh隧道之后,主机A想要登陆到主机C,访问localhost:22022即可(在A上执行命令)

1
ssh -p 22022 user@localhost

远程转发

远程转发指的是在远程 ssh 服务器建立的转发规则,含义跟本地转发正好反过来:建立本地计算机到远程 ssh 服务器的隧道以后,本地转发是通过本地计算机访问远程 ssh 服务器,而远程转发则是通过远程 ssh 服务器访问本地计算机。

1
2
3
4
5
6
7
场景:
A->C,某服务不可直连,C处于内网中被保护,A处于外网,无法连接到C
B->C,某服务可直连,B和C同在内网中,相互之间可以正常通讯
B->A,SSH可直连,B虽然处于内网中,但是可以和外网的A正常连接

实现:(远程转发,在B上执行命令)
A->B->C,利用B作为跳板,实现A对C的访问

远程转发的命令格式为(在跳板B上执行命令)

1
ssh -R A-tunnel-port:C-host:C-port A-host

其中:

  • A-tunnel-port:主机A上的端口,作为隧道入口
  • C-host:主机C,可以是C的内网ip,只要B可以访问即可
  • C-port:主机C的目标端口,如果需要ssh访问主机C,那么使用22号端口
  • A-host:主机A

例如(在跳板B上执行命令)

1
ssh -R 22022:xxx.xxx.xxx.xxx:22 user@A-host

在建立了ssh隧道之后,主机A想要登陆到主机C,访问localhost:22022即可(在A上执行命令)

1
ssh -p 22022 user@localhost

动态转发

动态转发指的是本机与 ssh 服务器之间创建了一个加密连接,本机内部针对某个端口的通信都通过这个加密连接转发,由ssh服务器代劳。 动态转发只是把本地端口绑定到 ssh 服务器,至于 ssh 服务器要去访问哪一个网站,完全取决于通信的要求,所以叫做动态转发。

一个常见的使用场景是:对所有外部网站的访问都转发到ssh服务器,实际上是用ssh服务器去访问。

这里有一个问题:之前使用固定的端口转发时,应用的访问请求都是明确指向被转发的端口X,但现在应用的访问请求必须指向目标,以指定动态端口转发的目标,此时如何让数据全部从端口X进入ssh隧道?我们必须在系统或应用(浏览器等)中手动设置一个使用SOCKS5协议、服务器为localhost、端口为X的代理,利用代理使这些网络请求全部走端口X。

应用的网络请求从端口X进入ssh隧道,抵达ssh服务器后将其中的目标信息解析出来,ssh服务器访问目标后再将结果通过隧道返回。

动态转发的命令格式为

1
ssh -D local-port tunnel-host

例如使用本地的2121端口

1
ssh -D 2121 tunnel-host

这里会自动登陆到tunnel-host,但是无所谓,也可以使用-N不登陆,此时会处于阻塞状态。(关闭这个会话则转发随之停止)

curl进行网络访问时需要加上-x选项设置SOCKS5代理

1
curl -x socks5://localhost:2121 www.baidu.com

注意wget不支持socks5代理,只支持http代理和https代理。

补充

ssh端口转发除了上面最基本的三个主机之间的操作,还可以加上更多的跳板服务器,此时需要在不同的主机上执行转发命令。

如果需要经常进行端口转发,还可以把转发写入配置文件中,这里不作讨论。

代理转发

-W选项要求openssh版本高于7.3,更低版本可以使用netcat工具进行替代。

这部分内容不作详细讨论,只是提供一个典型的例子:内网中有主机A和主机B,主机A无法可以连通github,但是主机B可以,那么我们可以在主机A中添加如下配置

1
2
3
4
5
6
7
8
Host B
...

Host github.com
User git
HostName github.com
ProxyCommand ssh -q -W %h:%p B
ForwardAgent yes

主机A就会通过主机B来连接github,注意此时我们仍然使用的是主机A的密钥对,需要保证公钥已经被添加到github中,密钥不需要存储在跳板机B中。

Github

除了远程登录服务器,使用Github也是配置ssh密钥的常见需求,直接在本地生成密钥对,然后把公钥上传到Github即可。 可以用下面的语句测试ssh配置是否正确

1
ssh -T git@github.com

在大部分情况下直连Github是可以的,但是偶尔会出现连接超时,无法推送和拉取Github仓库,有很多种解决办法,原理各不相同,而且都无法保证奏效。

  • 可以尝试换一个网络,手机热点通常比校园网更好使;

  • 对于使用http/https的远程仓库,可以用下面的命令让其走代理,注意修改代理的端口号(加上--global全局生效)

    1
    2
    git config http.proxy http://127.0.0.1:7892
    git config https.proxy https://127.0.0.1:7892

  • 可以参考下面的socks5代理配置(适合Windows,对于Linux代理命令略有不同),这里注意修改为clash所设置的端口(默认7890,我改成了7892);

    1
    2
    3
    Host github.com
    User git
    ProxyCommand connect -S 127.0.0.1:7892 %h %p

  • 可以使用ssh.github.com代替github.com,这会把ssh连接从22改到443,某些情况下会好很多(参考官方文档)。建议在.ssh/config中直接覆盖修改,而不是修改git远程仓库的URL;

    1
    2
    3
    4
    Host github.com
    User git
    Port 443
    HostName ssh.github.com

将代理转发和ssh.github.com结合可以得到如下的方案

1
2
3
4
5
6
7
8
Host B
...

Host github.com
User git
HostName ssh.github.com
ProxyCommand ssh -q -W %h:%p B
ForwardAgent yes

各种方案可以参考一文让你了解如何为 Git 设置代理