跳转到内容

OpenSSH/食谱/公钥认证

100% developed
来自维基教科书,开放世界中的开放书籍

 

如果正确使用,认证密钥可以提高效率。作为额外优势,密码和私钥永远不会离开客户端[1]。对于面向外部的系统,通常建议使用基于密钥的认证,以便可以关闭密码认证。


基于密钥的认证

[编辑 | 编辑源代码]

OpenSSH 可以使用公钥密码学进行认证。在公钥密码学中,加密和解密是非对称的。密钥成对使用,公钥用于加密,私钥用于解密。 ssh-keygen(1) 实用程序可以创建 RSA、Ed25519、ECDSA、Ed25519-SK 或 ECDSA-SK 密钥用于认证。虽然 DSA 密钥仍然可以创建,但大小固定为 1024 位,不再推荐使用,应避免使用。RSA 密钥允许从 1024 位开始变化。默认值现在是 3072。但是,在 2048 位之后,只有有限的益处,这使得椭圆曲线算法更可取。ECDSA 的大小可以是 256、384 或 521 位。Ed25519、Ed25519-SK 和 ECDSA-SK 密钥的长度都固定为 256 位。较短的密钥速度更快,但安全性较低。较长的密钥处理起来要慢得多,但提供了更好的保护,直到一定程度为止。

密钥可以命名以帮助记住它们的用途。因为密钥文件可以命名为任何东西,所以可以拥有许多密钥,每个密钥都为不同的服务或任务命名。公钥末尾的注释字段也可以在帮助对密钥进行排序方面发挥作用,如果你有很多密钥或不经常使用它们。

基于密钥的认证过程使用这些密钥进行几次交换,使用密钥来加密和解密一些简短的消息。在开始时,客户端公钥的副本存储在服务器上,客户端私钥存储在客户端上,两者都保留在各自的位置。私钥永远不会离开客户端。当客户端首次联系服务器时,服务器会使用客户端公钥加密一个随机数,并将该加密的随机数作为挑战返回给客户端。客户端通过使用匹配的私钥解密消息并提取随机数来响应挑战。然后,客户端对会话 ID 和来自挑战的随机数进行 MD5 哈希,并将该哈希返回给服务器。然后,服务器对会话 ID 和随机数进行自己的哈希,并将结果与客户端返回的哈希进行比较。如果匹配,则允许登录。如果不匹配,则尝试服务器上注册为属于同一帐户的下一个公钥,直到找到匹配项或所有密钥都已尝试或达到最大失败次数为止。[2]

当在客户端使用代理来管理认证时,该过程类似。不同之处在于 ssh(1) 将挑战传递给代理,代理计算响应并将其传递回 ssh(1),然后 ssh(1) 将代理的响应传递回服务器。

公钥认证基础

[编辑 | 编辑源代码]

公钥认证需要一对匹配的密钥, ssh-keygen(1) 用于生成密钥对。在这对密钥中,公钥必须正确存储在远程主机上。在服务器上,公钥被添加到该远程用户帐户指定的 authorized_keys 文件中。私钥安全地存储在客户端上。一旦密钥准备就绪,就可以反复使用。

基于密钥的认证准备工作包含四个步骤。

1) 准备密钥存放的目录。如果远程机器上不存在 authorized_keys 文件或 .ssh 目录,或者客户端机器上不存在 .ssh 目录,请创建它们并正确设置权限。在客户端上只需要一个目录,但该目录不应被除所有者以外的任何帐户写入。

$ mkdir ~/.ssh/
$ chmod 0700 ~/.ssh/

在远程机器上,需要 .ssh 目录和一个用于存储公钥的特殊文件,默认情况下是 authorized_keys

$ mkdir ~/.ssh/
$ touch ~/.ssh/authorized_keys
$ chmod 0700 ~/.ssh/
$ chmod 0600 ~/.ssh/authorized_keys

2) 创建密钥对。此处的示例在 ~/.ssh 目录中创建了一个 Ed25519 密钥对。-t 选项指定密钥类型,-f 选项指定密钥文件的名称。最好给密钥文件赋予描述性的名称,尤其是在管理大量密钥时。下面,公钥将被命名为 mykey_ed25519.pub,私钥将被命名为 mykey_ed25519。请务必输入可靠的密码,使用 128 位 AES 加密私钥。

$ ssh-keygen -t ed25519 -f ~/.ssh/mykey_ed25519

Ed25519、Ed25519-SK 和 ECDSA-SK 密钥具有固定长度。对于 RSA 和 ECDSA 密钥,-b 选项设置使用的位数。

从版本 6.5 开始,可以使用新的私钥格式,使用 bcrypt(3) 密钥派生函数 (KDF) 来更好地保护静止状态下的密钥。这种新格式始终用于 Ed25519 密钥,并且将来可能会成为所有密钥的默认格式。但目前,可以通过 ssh-keygen(1) 中的 -o 选项在生成或保存其他类型密钥时请求它。

$ ssh-keygen -o -b 4096 -t rsa -f ~/.ssh/mykey_rsa

有关新格式的详细信息可以在源代码中的 PROTOCOL.key 文件中找到。

3) 将密钥放到正确的位置。仅将公钥传输到远程机器。

$ ssh-copy-id -i ~/.ssh/mykey_ed25519 [email protected]

如果 ssh-copy-id(1) 不可用,可以使用任何不会换行的编辑器。例如,nano(1) 可以使用 -w 选项启动,以防止长行换行。另一种方法是永久设置它,方法是编辑 nanorc(5)。但是,在编辑 authorized_keys 文件以添加密钥时,密钥本身必须完整无缺地位于文件中的单行上。

然后,如果它们尚未存在于客户端上,请将公钥和私钥都传输到那里。通常最好将公钥和私钥都保存在 ~/.ssh/ 目录中,尽管在此步骤之后客户端不需要公钥,并且可以再次生成。如果需要。

4) 测试密钥。

在仍然登录的情况下,使用客户端在新的窗口中启动另一个 SSH 会话,并尝试使用私钥从客户端进行身份验证以连接到远程机器。

$ ssh -i ~/.ssh/mykey_ed25519 -l fred remotehost.example.org

-i 选项告诉 ssh(1) 尝试哪个私钥。仅在验证基于密钥的认证有效后,关闭原始 SSH 会话。

一旦基于密钥的身份验证被验证为工作正常,就可以使用 ssh_config(5) 在客户端创建永久快捷方式,下面将进一步解释。特别是,请参阅 IdentityFileIdentitiesOnlyAddKeysToAgent 配置指令,仅举三例。

基于密钥的身份验证的故障排除

如果服务器拒绝接受密钥并切换到下一个身份验证方法(例如:“服务器拒绝我们的密钥”),那么在服务器端有几个可能的错误需要查找。

最常见的错误之一是文件和目录权限错误。授权密钥文件必须由相关用户拥有,并且不能由组写入。密钥文件的目录也不能由组或世界写入。

$ chmod u=rwx,g=rx,o= ~/.ssh
$ chmod u=rw,g=,o=  ~/.ssh/authorized_keys

另一个可能发生的错误是,如果远程主机上的 authorized_keys 文件中的密钥被换行符或其他空格隔开,则该密钥会损坏。可以通过合并行并删除空格,或更仔细地重新复制密钥来解决此问题。

而且,虽然应该不言而喻,但密钥对的两个部分需要匹配。服务器上的公钥需要与客户端持有的私钥匹配。如果公钥丢失,则可以使用 -y 选项生成一个新的公钥,但反之则不行。如果私钥丢失,则应擦除公钥,因为它不再有任何用处。如果一个帐户使用多个密钥,添加注释可能是个好主意。在客户端,最好了解密钥适用于哪个服务器,可以通过文件名本身或注释字段来实现。可以使用 -C 选项添加注释。

$ ssh-keygen -t ed25519 -f ~/.ssh/mykey_ed25519 -C "web server mirror"

在服务器上,如果一个帐户中存在多个公钥,则注释密钥来自哪个客户端非常重要。如果注释不存在,则可以在服务器上将注释添加到授权密钥文件的最后一列中。同样,授权密钥文件的格式在 sshd(8) 手册页的“AUTHORIZED_KEYS FILE FORMAT”部分给出。如果密钥没有标签,它们可能很难匹配,这可能正是你想要的,也可能不是。

将密钥永久关联到服务器

[edit | edit source]

密钥可以在运行时指定,但为了节省重复输入相同路径的时间,ssh_config(5) 中的 Host 指令可以将特定设置应用于目标主机。在这种情况下,通过更改 ~/.ssh/config,可以将特定的密钥分配给在连接到该特定主机时自动尝试。在将以下几行添加到 ~/.ssh/config 后,只需要键入 ssh web1 就可以使用该服务器的密钥进行连接。

Host web1
	Hostname 198.51.100.32
	IdentitiesOnly yes
	IdentityFile /home/fred/.ssh/web_key_ed25519

下面的 ~/.ssh/configserverserver.example.org 使用不同的密钥,无论它们是否解析到同一台机器。这是可能的,因为传递给 ssh(1) 的主机名参数在匹配之前不会转换为规范化的主机名。

Host server
	IdentitiesOnly yes
	IdentityFile /home/fred/.ssh/key_a_rsa

Host server.example.org
	IdentitiesOnly yes
	IdentityFile /home/fred/.ssh/key_b_rsa

在这个例子中,较短的名称首先被尝试,当然也可以使用不太模糊的快捷方式。配置文件按照首个匹配的原则进行解析。因此,最具体的规则放在开头,最通用的规则放在结尾。

加密的家目录

[edit | edit source]

当使用加密的家目录时,密钥必须存储在未加密的目录中。这意味着存储在实际家目录之外的某个地方,这意味着 sshd(8) 需要被适当地配置,以便在该特殊位置找到密钥。

以下是一种解决访问问题的方法。每个用户在 /etc/ssh/keys/ 下都有一个子目录,他们可以使用它来存储 authorized_keys 文件。这在服务器的配置文件 /etc/ssh/sshd_config 中设置。

AuthorizedKeysFile      /etc/ssh/keys/%u/authorized_keys

为密钥设置特殊位置为密钥的管理提供了更多可能性,如果多个密钥文件位置用空格隔开,则可以指定多个密钥文件位置。用户不必对 authorized_keys 文件具有写入权限。只需要读取权限就可以登录。但是,如果用户被允许添加、删除或更改他们的密钥,那么他们将需要对文件进行写入操作才能做到这一点。

加密家目录的一个症状是,基于密钥的身份验证仅在您已经登录到同一帐户时有效,但在尝试进行第一次连接并首次登录时会失败。

有时还需要在身份验证后立即从 /etc/ssh/sshrc 中添加脚本或调用程序来解密家目录。

无密码登录

[edit | edit source]

允许无密码登录的一种方法是遵循上述步骤,但在创建密钥时,在提示输入密码时,只需不输入密码即可。请注意,使用没有密码的密钥风险很大,因此密钥文件应受到非常好的保护并进行跟踪。这包括它们只能用作下面描述的单用途密钥。及时轮换密钥变得尤为重要。一般来说,不建议创建没有密码的密钥。一个更好的解决方案是设置密码,并结合使用身份验证代理和单用途密钥。如今,大多数桌面环境会自动启动 SSH 代理。如果它是,它将在 SSH_AUTH_SOCK 环境变量中可见。在具有代理的帐户上,ssh-add(1) 可以将私钥加载到可用的代理中。

$ ssh-add ~/.ssh/mykey_ed25519

此后,客户端将在需要时自动检查代理以获取密钥。如果代理中有许多密钥,则需要设置 IdentitiesOnly。请参阅上面关于使用 ~/.ssh/config 的部分。请参阅下面的 [OpenSSH/Cookbook/Public_Key_Authentication#Key-based_Authentication_Using_an_Agent 基于密钥的身份验证使用代理]。

同时要求密钥和密码

[edit | edit source]

虽然用户应该为他们的密钥设置强密码,但无法强制或验证这一点。事实上,由于私钥及其密码从未离开客户端机器,服务器无法对它们有任何影响。相反,可以同时要求密钥和密码。从 OpenSSH 6.2 开始,服务器可以使用 AuthenticationMethods 指令要求用户使用多种身份验证方法登录。

AuthenticationMethods publickey,password

来自 http://man.openbsd.org/sshd_config.5 sshd_config(5)] 的这个例子要求用户首先使用密钥进行身份验证,只有当密钥成功时才会查询密码。因此,使用该配置,如果没有先使用有效的密钥进行身份验证,就无法进入系统密码提示符。更改参数的顺序会更改身份验证方法的顺序。

要求两个或更多个密钥

[edit | edit source]

从 OpenSSH 6.8 开始,服务器现在会记住哪些公钥已用于身份验证,并拒绝接受之前使用过的密钥。这允许设置要求用户使用两个不同的公钥进行身份验证,例如一个在文件系统中,另一个在硬件令牌中。

AuthenticationMethods publickey,publickey

AuthenticationMethods 指令(无论用于密钥还是密码),也可以在服务器上的 Match 指令下设置,以便仅应用于某些组或情况。

要求特定类型的密钥进行身份验证

[edit | edit source]

同样,从 OpenSSH 6.8 开始,PubkeyAcceptedKeyTypes 指令(后来改为 PubkeyAcceptedAlgorithms)可以指定哪些密钥算法可用于身份验证。不在逗号分隔的模式列表中的算法不允许使用。

PubkeyAcceptedAlgorithms ssh-ed25519*,ssh-rsa*,ecdsa-sha2*,sk-ssh-ed25519*,sk-ecdsa-sha2*

模式列表中可以是实际的密钥类型,也可以是模式。模式列表中不允许使用空格。可以使用客户端的 -Q 选项找到用于身份验证的受支持的密钥类型的完整列表。以下两行是等效的。

$ ssh -Q key-sig | sort

$ ssh -Q PubkeyAcceptedAlgorithms | sort

对于基于主机的身份验证,HostbasedAcceptedAlgorithms 指令决定哪些密钥类型允许用于身份验证。

使用代理的基于密钥的身份验证

[编辑 | 编辑源代码]

当使用身份验证代理(例如 ssh-agent(1))时,它通常应该在会话开始时启动,并用于启动登录会话或 X 会话,以便指向代理及其 UNIX 域套接字的环境变量传递给每个后续 shell 和进程。许多桌面发行版在登录或启动时自动执行此操作。

启动代理需要设置一对环境变量

  • SSH_AGENT_PID : 代理的进程 ID
  • SSH_AUTH_SOCK : UNIX 域套接字的文件名和完整路径

各种 SSH 和 SFTP 客户端会自动找到这些变量,并在需要身份验证时使用它们来联系代理并尝试。但是,主要使用 SSH_AUTH_SOCK。如果 shell 或桌面会话是使用 ssh-agent(1) 启动的,那么这些变量已经设置并可用。如果它们不可用,则需要在每个 shell 内或每个应用程序中手动设置变量,以便使用代理,或者使用客户端配置文件中的指令 IdentityAgent 指向代理的套接字。

代理可用后,需要加载相关的私钥才能使用代理。私钥加载到代理后,就可以多次使用。私钥使用 ssh-add(1) 加载到代理中。

$ ssh-add /home/fred/.ssh/mykey_ed25519
Identity added: /home/fred/.ssh/mykey_ed25519 (/home/fred/.ssh/mykey_ed25519)

密钥在代理运行期间一直保留在代理中,除非另有说明。可以使用启动代理本身时的 -t 选项,或使用 ssh-add(1) 加载密钥时设置超时。在这两种情况下,-t 选项都将设置一个超时间隔,在此之后,密钥将从代理中清除。

$ ssh-add -t 1h30m /home/fred/.ssh/mykey_ed25519
Identity added: /home/fred/.ssh/mykey_ed25519 (/home/fred/.ssh/mykey_ed25519)
Lifetime set to 5400 seconds

选项 -l 将列出代理中所有身份的指纹。

$ ssh-add -l                                    
256 SHA256:77mfUupj364g1WQ+O8NM1ELj0G1QRx/pHtvzvDvDlOk mykey for task x (ED25519)
3072 SHA256:7unq90B/XjrRbucm/fqTOJu0I1vPygVkN9FgzsJdXbk myotherkey rsa for task y (RSA)

也可以使用 -d 从代理中删除单个身份,如果按文件名标识,则会一次删除一个。但是,只有在给出文件名且没有给出要删除的私钥文件名时,-d 才会静默失败。使用 -D 而不是 -d 将一次删除所有身份,而无需按名称指定任何身份。

默认情况下,ssh-add(1) 使用通过环境变量 SSH_AUTH_SOCK 中命名的套接字连接的代理(如果已设置)。目前,这是它唯一的选项。但是,对于 ssh(1),使用环境变量的另一种方法是客户端配置文件指令 IdentityAgent,它告诉 SSH 客户端使用哪个套接字与代理通信。如果环境变量和配置文件指令同时存在,那么 IdentityAgent 中的值将优先于环境变量中的值。IdentityAgent 也可以设置为 none,以防止连接尝试使用任何代理。

客户端配置文件指令 AddKeysToAgent 也可以在将密钥添加到代理时很有用。当设置此选项时,如果密钥尚未加载,它会在第一次调用密钥时自动将密钥加载到正在运行的代理中。同样,IdentitiesOnly 指令可以确保在第一次尝试时提供相关密钥。与其在每次运行客户端时键入这些指令,不如将它们添加到 ~/.ssh/config 中,从而为指定的宿主连接自动添加这些指令。

代理转发

[编辑 | 编辑源代码]

代理转发是通过一个或多个中间主机的一种方法。但是,-J 选项用于 ProxyJump 会是一个更安全的选择。请参阅跳转主机部分中的 通过网关或两个网关,了解这方面的知识。使用代理转发,中间机器会在客户端和最终目标之间来回转发挑战和响应。这带来了一些风险,但省去了在这些中间机器上使用密码或持有密钥的必要性。

代理转发的主要优点是私钥本身不需要放在任何远程机器上,从而阻止了对私钥的无意文件系统访问。[3] 另一个优点是,用户已认证的实际代理不会转到任何地方,因此不易受到分析。

代理的一个风险是,如果权限允许,它们可以被重复使用以尾随进入。密钥无法以这种方式复制,但当权限不正确时,身份验证是可能的。请注意,禁用代理转发不会提高安全性,除非用户也被拒绝 shell 访问权限,因为他们总是可以安装自己的转发器。

可以通过在将密钥添加到代理时添加 -c 选项来确认每次使用密钥,从而减轻代理转发的风险。这需要设置 SSH_ASKPASS 变量,并使其可供代理进程使用,但会在每次远程系统使用密钥时,在运行代理的主机上生成一个提示。因此,如果要通过一个或多个中间主机,通常最好让 SSH 客户端使用带有 -W-J 的 stdio 转发。

在客户端侧,代理转发默认情况下是禁用的,因此如果要使用它,必须显式启用它。将以下行添加到 ssh_config(5) 中,以启用特定服务器的代理转发

        Host gateway.example.org
        ForwardAgent yes

在服务器侧,默认配置文件允许身份验证代理转发,因此要使用它,不需要在服务器侧做任何操作,只需在客户端侧做操作。但是,同样,最好还是看看 ProxyJump 而不是代理转发。

旧样式,更安全的 SSH 代理转发
[编辑 | 编辑源代码]

通过一个或多个中间主机的最佳方法是使用 ProxyJump 选项,而不是身份验证代理转发,从而避免将任何私钥暴露给风险。如果必须使用身份验证代理转发,那么出于遵循最小特权原则的考虑,建议转发包含最少必要数量密钥的代理。有几种方法可以解决这个问题。

在 8.8 及更早版本中,一种部分解决方案是创建一个一次性、临时的代理,专门用于保存当前任务所需的密钥。另一种部分解决方案是,在操作系统级别设置一个用户可访问的服务,然后使用 ssh_config 来完成剩余的操作。

可以通过创建特殊的 shell 别名或函数来启动每个会话唯一的临时代理,从而自动启动临时代理。该函数或别名可以被编写为需要确认每个请求的签名。以下示例是一个别名,基于 Vincent Bernat[4] 在 SSH 代理转发方面的更新博客文章。

$ alias assh="ssh-agent ssh -o AddKeysToAgent=confirm -o ForwardAgent=yes"

请注意 ssh-agent(1) 的使用。当调用该别名时,SSH 客户端将使用一个独特的、临时的支持密钥代理启动。该别名设置一个新的代理,包括设置两个环境变量,然后在调用客户端时设置两个客户端选项。这种安排仍然会检查 ssh_config(5) 中的其他选项和设置。当 SSH 会话结束时,启动它的代理结束并消失,从而自动清理自身。

另一种方法是依赖客户端的配置文件来完成一些设置。这些方法主要依赖于 ssh_config(5),但仍然需要一种独立的方法来启动一个临时代理,因为 OpenSSH 客户端在读取配置文件之前就已经在运行,因此不受配置文件导致的环境变量更改的影响,而代理信息是通过环境变量传递的。但是,当用于与身份验证代理通信的 UNIX 域套接字的路径预先确定时,IdentityAgent 选项可以指向它,一旦一次性代理[5] 实际启动。以下方法在连接到两个特定域中的任何一个时,使用特定代理的预定义套接字

Host *.wikimedia.org *.wmflabs.org
        User fred
        IdentitiesOnly yes
        IdentityFile %d/.ssh/id_cloud_01
        IdentityAgent /run/user/%i/ssh-cloud-01.socket
        ForwardAgent yes
        AddKeysToAgent yes

%d 代表主目录的路径,%i 代表当前帐户的用户 ID (UID)。在某些情况下,%i 标记在配置 文件内设置 IdentityAgent 选项时也会很有用。同样,在转发代理时,请注意转发代理中使用哪些密钥。有关更多此类缩写,请参阅 ssh_config(5) 中的“TOKENS”部分。

使用这些配置设置,身份验证代理必须已经在运行,并且在启动 SSH 客户端以使该配置生效之前指向指定的套接字。此外,它应该将套接字放在任何其他帐户都无法访问的目录中。ssh-agent(1) 必须使用 -a 选项来命名套接字。

$ ssh-agent -a /run/user/${UID}/ssh-cloud-01.socket

该代理配置可以手动启动,也可以通过脚本或服务管理器启动。但是,出于隐私和安全考虑,应避免代理转发。配置指令 ProxyJump 是最好的替代方案,在旧系统上,使用 ProxyCommandnetcat 进行主机遍历是更可取的。同样,请参阅有关 代理和跳转主机 的部分,了解这些方法的使用方式。

新的 SSH 代理目标约束
[edit | edit source]

从 8.9 版本开始,ssh-agent(1) 将允许代理限制使用哪些主机进行身份验证,如 ssh-add(1) 使用 -h 选项指定的。这些约束是通过两个代理协议扩展和对公钥身份验证协议的修改添加的。此功能可能会发展,但目前的结果是帐户身份验证的密钥可以通过四种方式加载到代理中

  • 转发没有限制(不推荐)
  • 仅限本地使用,这些密钥不会转发
  • 转发,但仅限于特定远程主机
  • 通过指定路由转发到特定远程主机

目的是让限制安全失败,这样就不会在路由中的一台或多台主机缺乏必要的协议功能时允许身份验证。在密钥加载后,目标和路由无法修改,但可以加载到同一个目标的多条路由,并且路由可以是任意数量的跳跃。如果需要更改路由,则必须使用新路由将密钥重新加载到代理中。

客户端的一般默认行为是将密钥保留在代理中以供本地使用。但是,可以通过在启动客户端时添加 -a 选项,或者在相关配置块中将 ssh_config(5) 中的 ForwardAgent 指令设置为 'no' 来显式强制执行。

为了加载用于无限制转发的密钥(这不是最佳做法),只需使用 ssh-add(1) 像往常一样添加它们。然后,在客户端中使用 -A 选项,或者在相关配置块中将 ssh_config(5) 中的 ForwardAgent 指令设置为 'yes'。

为了将密钥限制为仅连接到特定的远程主机,或者将密钥加载到特定远程主机以通过一个或多个中间主机进行转发,请在将密钥加载到代理中时使用 -h 选项。这里,一个密钥只能用于连接到特定目标。

$ ssh-agent -h server.example.org server.key.ed25519

如果通过中间系统,最好的方法是使用 ProxyJump,即 SSH 客户端的 -J 选项。如果必须允许代理转发,那么最严格的方法是约束哪些系统可以使用密钥,同样可以使用 -h 选项。

$ ssh-agent -h middle.example.org -h "middle.example.org>server.example.org" server.key.ed25519

可以包含多个步骤,甚至可以包含多条路由。它们只需要显式枚举,尽管模式仍然可以用于目标主机以及特定名称。链中的每个主机都必须支持这些协议扩展才能完成连接。

任何被指定用于转发的密钥都无法用于对除那些被明确识别为转发目标以外的任何其他主机进行身份验证。这些允许的主机通过 known_hosts 文件或在使用 ssh-add(1) 加载密钥时由 -H 选项指定的另一个文件中的主机密钥或主机证书进行识别。如果在将密钥加载到代理时不使用 -H,则将使用默认的已知主机文件:~/.ssh/known_hosts/etc/ssh/ssh_known_hosts~/.ssh/known_hosts2/etc/ssh/ssh_known_hosts2

对于密钥,必须认真维护 [6] known_hosts 列表,也许可以使用 UpdateHostkeysCanonicalizeHostname 客户端配置指令。使用证书要求代理只需要知道证书颁发机构 (CA)。

同样,请参阅跳转主机部分中的 通过网关,了解如何在不需要转发 SSH 代理的情况下通过一台或多台中间机器。

检查代理中的特定密钥

[edit | edit source]

ssh_add(1) 实用程序的 -T 选项可以通过查找匹配的公钥来测试特定私钥是否在代理中可用。这在 shell 脚本中很有用。

#!/bin/sh

key=/home/fred/.ssh/some.key.ed25519.pub

if ssh-add -T ${key}; then
        echo "Key ${key} Found"
else
        echo "Key ${key} missing"
fi

或者,它也可以使用另一种语法完成,无论是脚本还是交互式 shell 会话,

$ key=/home/fred/.ssh/some.key.ed25519.pub
$ ssh-add -T ${key} && echo "Key found" || echo "Key missing"

但是,如果需要将密钥添加到代理中,则 AddKeysToAgent 客户端配置选项可以确保在任何给定的登录会话期间第一次使用时将特定密钥添加到 SSH 代理中。这可以通过使用 -o AddKeysToAgent=yes 作为运行时参数来完成,也可以通过修改 ssh_config(5) 来完成,具体取决于情况。

Host www
        HostName www.example.com
        IdentityFile %d/.ssh/www.ed25519
        IdentitiesOnly yes
        AddKeysToAgent yes

使用配置 文件中的这些选项,第一次运行 ssh www 时,指定的密钥将被添加到代理中并保持可用。

使用硬件安全令牌进行基于密钥的身份验证

[edit | edit source]

虽然独立密钥已经存在很长时间了,但从 8.2 版本开始,可以使用由硬件安全令牌支持的密钥,例如 OnlyKey、Yubikey 或其他许多密钥,但通过 FIDO2 协议。通用第二因素 (U2F) 身份验证通过 FIDO2 在 OpenSSH 中直接受支持,不需要第三方软件。目前,有两种类型的硬件支持的密钥,ECDSA-SK 和 Ed25519-SK,但只有最新的硬件令牌支持后者。如果令牌固件不支持 Ed25519-SK 密钥格式,那么在尝试使用该密钥类型时,将显示以下错误消息

Key enrollment failed: invalid format

如果受支持,可以使用 ssh-keygen(1) 创建这两种密钥类型。步骤几乎与创建普通密钥相同,但令牌必须先对系统可用(插入)。然后,如果需要,必须输入令牌的 PIN 码,并触摸令牌或以其他方式激活它。之后,密钥创建将像往常一样进行。注意由 -t 选项指定的密钥类型。

$ ssh-keygen -t ed25519-sk -f /home/fred/.ssh/server.ed25519-sk -C "web server for fred"
Generating public/private ed25519-sk key pair.
You may need to touch your authenticator to authorize key generation.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/fred/.ssh/server.ed25519-sk
Your public key has been saved in /home/fred/.ssh/server.ed25519-sk.pub
The key fingerprint is:
SHA256:41wVVDnKJ9gKr2Sj4CFuYMhcNvYebZ6zq0PWyP4rRDo web server
The key's randomart image is:
+[ED25519-SK 256]-+
|           .o... |
|             .o  |
|           +.. . |
|   = .  . ..= .  |
|+ + * + So.. o   |
|o+.EoO *+oo      |
|.o oBo+++o       |
|  o .=.+.        |
| .   .===        |
+----[SHA256]-----+

创建完成后,公钥和私钥文件像处理任何其他类型的密钥一样处理。但在进行身份验证时,硬件令牌必须存在并在需要时激活。

$ ssh -i /home/fred/.ssh/server.ed25519-sk server.example.org
Enter passphrase for key '/home/fred/.ssh/server.ed25519-sk': 
Confirm user presence for key ED25519-SK SHA256:41wVVDnKJ9gKr2Sj4CFuYMhcNvYebZ6zq0PWyP4rRDo

生成的私钥文件实际上不是密钥本身,而是“密钥句柄”,硬件安全令牌使用该句柄根据需要在实际使用时派生真正的私钥 [7]。因此,没有配套的硬件令牌,硬件支持的私钥文件毫无用处。这也意味着这些密钥文件不能跨硬件令牌移植,例如当有多个令牌作为备用或备份时,即使由同一个帐户使用也是如此。因此,当使用多个硬件令牌时,必须为每个令牌生成不同的密钥对。

硬件安全令牌驻留私钥

[edit | edit source]

可以将私钥存储在令牌本身中,但目前它无法直接从令牌中使用,必须先将其保存为文件。此外,密钥只能在使用 ssh-keygen(1) 中的 -O resident 选项创建时加载到 FIDO 身份验证器中。否则,过程与上述相同。

$ ssh-keygen -O resident -t ed25519-sk -f /home/fred/.ssh/server.ed25519-sk -C "web server for fred"
. . .

如有需要,可以使用-K选项从FIDO2硬件令牌中提取居民密钥并保存到文件。在此阶段,可以向文件添加密码,但令牌本身不会保留任何密码,只有可选的PIN保护那里的密钥。

$ ssh-keygen -K
Enter PIN for authenticator: 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Saved ED25519-SK key to id_ed25519_sk_rk

$ mv -i id_ed25519_sk_rk /home/fred/.ssh/server.ed25519-sk

由于输出文件名是固定的,任何具有该名称的现有文件都可能被覆盖,但会先发出警告。但是,不建议将密钥保存在硬件令牌上,因为将其单独保存可以提供更多保护。

单用途密钥

[edit | edit source]

定制的单用途密钥可以消除许多管理活动中对远程root登录的使用。需要一个精心定制的sudoers以及一个非特权帐户。如果操作正确,它将提供足够的操作权限,遵循最小权限的安全原则。

单用途密钥与在sshd_config(5)中使用ForceCommand指令或在authorized_keys文件中使用command="..."指令一起使用。该方法是生成一个新的密钥对,将公钥传输到远程系统上的authorized-keys,然后将相应的命令或脚本添加到带有密钥的那一行。

$ grep '^command' ~/.ssh/authorized_keys
command="/usr/local/bin/somescript.sh" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEcgdzDvSebOEjuegEx4W1I/aA7MM3owHfMr9yg2WH8H

插入的command="..."指令将覆盖其他所有内容,并确保在仅使用该密钥登录时,仅运行脚本/usr/local/bin/somescript.sh。如果需要将参数传递给脚本,请查看SSH_ORIGINAL_COMMAND环境变量的内容,并在 case 语句中使用它。永远不要信任该变量的内容,也不要直接使用内容,始终间接使用。

单用途密钥可用于仅允许隧道而无其他操作。以下密钥将仅回显一些文本然后退出,除非在-N选项的非交互模式下使用。

$ grep '^command' ~/.ssh/authorized_keys
command="/bin/echo do-not-send-commands" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBzTIWCaILN3tHx5WW+PMVDc7DfPM9xYNY61JgFmBGrA

无论用户在使用该密钥登录时尝试什么操作,会话都将仅回显给定的文本然后退出。使用-N选项将禁用运行远程程序,允许连接保持打开状态,允许隧道。

$ ssh -L 3306:localhost:3306 \
	-i ~/.ssh/tunnel_ed25519 \
	-N \
	-l fred \
	server.example.com

这将创建一个隧道,即使在密钥配置将关闭交互式会话的情况下也会保持连接。另请参阅ssh(1)-n-f选项。

避免远程root访问的单用途密钥

[edit | edit source]

简单的方法是编写一个简短的 shell 脚本,将其放在/usr/local/bin/中,然后配置sudoers以允许其他非特权帐户仅运行该脚本,并且只能运行该脚本。

%wheel  ALL=(root:root) NOPASSWD: /usr/sbin/service httpd stop
%wheel  ALL=(root:root) NOPASSWD: /usr/sbin/service httpd start

然后密钥使用authorized_keys中的command="..."调用该脚本。这里,一个密钥启动web服务器,另一个密钥停止web服务器。

$ grep '^command' ~/.ssh/authorized_keys
command="/usr/bin/sudo /usr/sbin/service httpd stop" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEcgdzDvSebOEjuegEx4W1I/aA7MM3owHfMr9yg2WH8H
command="/usr/bin/sudo /usr/sbin/service httpd start" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMidyqZ6OCvbWqA8Zn+FjhpYE6NoWSxVjFnFUk6MrNZ4

构建单用途密钥时,复杂的程序(如rsync(1)tar(1)mysqldump(1)等)需要高级方法。对于它们,-v选项可以准确地显示传递给服务器的内容,以便正确设置sudoers。这样,它们就可以被限制为仅访问指定的文件系统部分。例如,以下是ssh -vrsync(1)的特定使用方式中显示的内容,注意“Sending command”行

$ rsync -e 'ssh -v' [email protected]:/etc/ ./backup/etc/
. . .
debug1: Sending command: rsync --server --sender -e.LsfxC . /etc/
. . .

然后可以将该输出添加到sudoers中,以便密钥只能执行该功能。

%backup ALL=(root:root) NOPASSWD: /usr/bin/rsync --server --sender -e.LsfxC . /etc/

然后,将所有这些结合在一起,帐户“backup”需要一个密钥

$ grep '^command' ~/.ssh/authorized_keys
command="/usr/bin/rsync --server --sender -e.LsfxC . /etc/" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMm0rs4eY8djqBb3dIEgbQ8lmdlxb9IAEuX/qFCTxFgb

这些程序中的许多都有一个---dry-run或等效选项。在确定正确设置时,请记住使用它。

对密钥的只读访问

[edit | edit source]

在某些情况下,需要防止帐户更改自己的身份验证密钥。但是,这种情况可能更适合使用证书。但是,如果使用密钥完成,则可以通过将密钥文件放在用户具有只读访问权限的外部目录中来实现,包括对目录和密钥文件的访问权限。然后,AuthorizedKeysFile指令指定sshd(8)查找密钥的位置,并可以指向密钥的安全位置,而不是默认位置。

一个很好的替代位置可能是新目录/etc/ssh/authorized_keys,它可以存储所选帐户的密钥文件。可以通过将设置放在Match指令下,使更改仅应用于一组帐户。大多数系统上的默认密钥位置通常是~/.ssh/authorized_keys

Match Group sftpusers
	AuthorizedKeysFile /etc/ssh/authorized_keys/%u

然后,那里的权限将允许读取密钥,但不能写入密钥

$ ls -dhln /etc/ssh/
drwxr-x--x 3 0 0 4.0K Mar 30 22:16 /etc/ssh/authorized_keys/

$ ls -dhln /etc/ssh/*.pub
-rw-r--r-- 1 0 0 173 Mar 23 13:34 /etc/ssh/fred
-rw-r--r-- 1 0 0  93 Mar 23 13:34 /etc/ssh/user1
-rw-r--r-- 1 0 0 565 Mar 23 13:34 /etc/ssh/user2
. . .

密钥甚至可以位于子目录中,但权限和所有权的相同限制适用。

对于chroot的SFTP,该方法与将密钥文件置于帐户无法访问的范围内的相同

Match Group sftpusers
	ChrootDirectory /home
	ForceCommand internal-sftp -d %u
	AuthorizedKeysFile /etc/ssh/authorized_keys/%u

当然,Match指令不是必需的。可以通过将指令放在服务器配置文件的主要部分中,使设置应用于所有帐户。

将公钥标记为已撤销

[edit | edit source]

密钥可以被撤销。已撤销的密钥可以存储在/etc/ssh/revoked_keys中,这是一个在sshd_config(5)中使用RevokedKeys指令指定的 文件,以便sshd(8)阻止尝试使用它们登录。如果尝试使用被撤销的密钥,客户端侧不会发出任何警告或错误。身份验证将简单地继续进行到下一个密钥或方法。

被撤销的密钥文件应包含一个已撤销的公钥列表,每行一个,这些密钥不再可用于连接到服务器。密钥不能包含任何额外的内容,例如登录选项,否则它将被忽略。如果在登录尝试期间尝试使用被撤销的密钥之一,服务器将简单地忽略它并继续进行下一个身份验证方法。将记录尝试的日志条目,包括密钥的指纹。有关此内容的更多信息,请参见有关记录的部分。

RevokedKeys /etc/ssh/revoked_keys

RevokedKeys配置指令在sshd_config(5)中默认情况下未设置。如果要使用它,则必须显式设置它。这又是一种情况,可能更适合通过使用证书来完成,因为证书可以设置任何秒、分钟、小时、天或周的有效期组合,而密钥则无限期有效。

密钥撤销列表

[edit | edit source]

密钥撤销列表(KRL)是表示已撤销密钥和证书的紧凑二进制形式。为了使用 KRL,服务器的配置文件必须使用RevokedKeys指令指向一个有效的列表。KRL 本身是用ssh-keygen(1)生成的,可以从头开始创建或就地编辑。这里,创建了一个新的 KRL,其中包含一个公钥

$ ssh-keygen -kf  /etc/ssh/revoked_keys -z 1 ~/.ssh/old_key_rsa.pub

这里,通过添加-u选项来更新现有 KRL

$ ssh-keygen -ukf /etc/ssh/revoked_keys -z 2 ~/.ssh/old_key_dsa.pub

KRL 就位后,就可以测试特定密钥或证书是否在撤销列表中。

$ ssh-keygen -Qf  /etc/ssh/revoked_keys ~/.ssh/old_key_rsa.pub

仅公钥和证书将被加载到 KRL 中。损坏或损坏的密钥将不会被加载,如果尝试加载,将产生错误消息。与常规RevokedKeys列表一样,目标为 KRL 的公钥不能包含任何额外的内容,例如登录选项,否则在尝试将其加载到 KRL 或在 KRL 中搜索它时会产生错误。

通过指纹验证主机密钥

[edit | edit source]

以上示例都是关于使用密钥对客户端进行服务器身份验证。密钥使用的另一个上下文是服务器向客户端标识自身,这会在每个非多路复用会话开始时自动发生。为了进行这种标识,客户端会从服务器获取一个公钥,通常是在第一次联系之前或之前,它可以随后使用该公钥来确保它再次连接到同一个服务器,而不是冒充者。在客户端上存储这些获取的主机密钥的默认位置是/etc/ssh/ssh_known_hosts(如果由系统管理员管理),或者~/.ssh/known_hosts(如果由客户端自己的帐户管理)。内容的格式是包含主机地址及其匹配公钥的一行。该文件在sshd(8)手册页的“SSH_KNOWN_HOSTS FILE FORMAT”部分中进行了详细描述。

首次连接到远程主机时,应验证服务器的主机密钥,以确保客户端连接到的是正确的机器,而不是冒名顶替者或其他任何东西。通常,此验证是通过比较服务器主机密钥的指纹来完成的,而不是尝试比较整个密钥本身。默认情况下,如果客户端在known_hosts注册表中未找到密钥,则客户端将显示指纹。

$ ssh -l fred server.example.org
The authenticity of host 'server.example.org (192.0.32.10)' can't be established.
ECDSA key fingerprint is SHA256:LPFiMYrrCYQVsVUPzjOHv+ZjyxCHlVYJMBVFerVCP7k.
Are you sure you want to continue connecting (yes/no)?

这可以与通过非带宽方式接收到的指纹进行比较,例如通过邮件、电子邮件、短信、快递等。具体来说,该示例将密钥的指纹表示为 base64 编码的 SHA256 校验和。这是默认样式。指纹也可以显示为十六进制的 MD5 哈希,方法是将客户端的FingerprintHash配置指令作为运行时参数传递,或者在ssh_config(5)中进行设置。

$ ssh -o FingerprintHash=md5 host.example.org
The authenticity of host 'host.example.org (192.0.32.203)' can't be established.
RSA key fingerprint is MD5:10:4a:ec:d2:f1:38:f7:ea:0a:a0:0f:17:57:ea:a6:16.
Are you sure you want to continue connecting (yes/no)?

但新版本中的默认值是 base64 中的 SHA256,它具有较低的碰撞概率。

在 OpenSSH 6.7 及更早版本中,客户端将指纹显示为十六进制 MD5 校验和,而不是当前使用的 base64 编码的 SHA256 校验和

$ ssh -l fred server.example.org
The authenticity of host 'server.example.org (192.0.32.10)' can't be established. 
RSA key fingerprint is 4a:11:ef:d3:f2:48:f8:ea:1a:a2:0d:17:57:ea:a6:16. 
Are you sure you want to continue connecting (yes/no)?

比较密钥的另一种方法是使用 ASCII 艺术可视化主机密钥。有关详细信息,请参阅下文。

下载密钥

[编辑 | 编辑源代码]

尽管通常在 SSH 客户端首次尝试连接时会显示主机密钥以供查看,但也可以使用 ssh-keyscan(1) 随时按需获取密钥。

$ ssh-keyscan host.example.org  
# host.example.org SSH-2.0-OpenSSH_8.2
host.example.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLC2PpBnFrbXh2YoK030Y5JdglqCWfozNiSMjsbWQt1QS09TcINqWK1aLOsNLByBE2WBymtLJEppiUVOFFPze+I=
# host.example.org SSH-2.0-OpenSSH_8.2
host.example.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9iViojCZkcpdLju7/3+OaxKs/11TAU4SuvIPTvVYvQO32o4KOdw54fQmd8f4qUWU59EUks9VQNdqf1uT1LXZN+3zXU51mCwzMzIsJuEH0nXECtUrlpEOMlhqYh5UVkOvm0pqx1jbBV0QaTyDBOhvZsNmzp2o8ZKRSLCt9kMsEgzJmexM0Ho7v3/zHeHSD7elP7TKOJOATwqi4f6R5nNWaR6v/oNdGDtFYJnQfKUn2pdD30VtOKgUl2Wz9xDNMKrIkiM8Vsg8ly35WEuFQ1xLKjVlWSS6Frl5wLqmU1oIgowwWv+3kJS2/CRlopECy726oBgKzNoYfDOBAAbahSK8R
# host.example.org SSH-2.0-OpenSSH_8.2
host.example.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDDOmBOknpyJ61Qnaeq2s+pHOH6rdMn09iREz2A/yO2m

获取密钥后,可以使用 ssh-keygen(1) 显示其指纹。这可以通过管道直接完成。

$ ssh-keyscan host.example.org | ssh-keygen -lf - 
# host.example.org SSH-2.0-OpenSSH_8.2
# host.example.org SSH-2.0-OpenSSH_8.2
# host.example.org SSH-2.0-OpenSSH_8.2
256 SHA256:sxh5i6KjXZd8c34mVTBfWk6/q5cC6BzR6Qxep5nBMVo host.example.org (ECDSA)
3072 SHA256:hlPei3IXhkZmo+GBLamiiIaWbeGZMqeTXg15R42yCC0 host.example.org (RSA)
256 SHA256:ZmS+IoHh31CmQZ4NJjv3z58Pfa0zMaOgxu8yAcpuwuw host.example.org (ED25519)

如果服务器上可用的公钥类型超过一种,则 ssh-keyscan(1) 将获取每个类型。如果通过stdin或文件提供的密钥超过一个,则ssh-keygen(1) 将按顺序处理它们。在 OpenSSH 7.2 之前,手动指纹识别是一个两步过程,密钥被读入文件,然后对其进行指纹识别处理。

$ ssh-keyscan -t ed25519 host.example.org > key.pub
# host.example.org SSH-2.0-OpenSSH_6.8
$ ssh-keygen -lf key.pub                       
256 SHA256:ZmS+IoHh31CmQZ4NJjv3z58Pfa0zMaOgxu8yAcpuwuw host.example.org (ED25519)

请注意,ssh-keyscan(1) 的一些输出被发送到stderr而不是stdout

可以在具有 awk(1)sed(1)xxd(1) 的系统上使用 awk(1)sed(1)xxd(1) 手动生成哈希或指纹。

$ awk '{print $2}' key.pub | base64 -d | md5sum -b | sed 's/../&:/g; s/: .*$//'
$ awk '{print $2}' key.pub | base64 -d | sha256sum -b | sed 's/ .*$//' | xxd -r -p | base64

如果主机名以明文形式存储,而不是以哈希形式存储,则可以从文件中找到所有具有与known_hosts中的密钥不同或新的密钥的主机。

$ ssh-keyscan -t rsa,ecdsa -f ssh_hosts | \
  sort -u - ~/.ssh/known_hosts | \
  diff ~/.ssh/known_hosts -

将 ssh-keyscan(1) 与 ssh_config(5) 一起使用

[编辑 | 编辑源代码]

实用程序 ssh-keyscan(1) 不解析 ssh_config(5)。这部分是为了保持代码库简单。有很多配置选项很难实现,包括但不限于ProxyJumpProxyCommandMatchBindInterfaceCanonicalizeHostname[8] 。可以通过将实用程序包装在简短的 shell 函数中来解析客户端配置文件中的主机名

my-ssh-keyscan() {
	for host in "$@" ; do
		ssh-keyscan $(ssh -G "$host" | awk '/^hostname/ {print $2}')
	done
}

该 shell 函数使用 ssh(1)-G选项使用 ssh_config(5) 解析每个主机名,然后检查结果主机名以获取 SSH 密钥。

ASCII 艺术可视化主机密钥

[编辑 | 编辑源代码]

可以与 SHA256 base64 指纹一起显示密钥的 ASCII 艺术表示形式

$ ssh-keygen -lvf key 
256 SHA256:BClQBFAGuz55+tgHM1aazI8FUo8eJiwmMcqg2U3UgWU www.example.org (ED25519)
+--[ED25519 256]--+
|o+=*++Eo         |
|+o .+.o.         |
|B=.oo.  .        |
|*B.=.o .         |
|= B *   S        |
|. .@ .           |
| +..B            |
|  *. o           |
| o.o.            |
+----[SHA256]-----+

在 OpenSSH 6.7 及更早版本中,指纹以 MD5 十六进制形式显示。

$ ssh-keygen -lvf key 
2048 37:af:05:99:e7:fb:86:6c:98:ee:14:a6:30:06:bc:f0 www.example.net (RSA) 
+--[ RSA 2048]----+ 
|          o      | 
|         o .     | 
|        o o      | 
|       o +       | 
|   .  . S        | 
|    E ..         | 
|  .o.* ..        | 
|  .*=.+o         | 
|  ..==+.         | 
+-----------------+

有关验证 SSH 密钥的更多信息

[编辑 | 编辑源代码]

可以通过比较 base64 编码的 SHA256 指纹,验证客户端或服务器上的密钥是否与已知的良好密钥匹配。

验证不确定的客户端密钥

[编辑 | 编辑源代码]

有时需要比较两个不确定的密钥文件,以检查它们是否属于同一密钥对。但是,公钥或多或少是可丢弃的。因此,在客户端机器上的此类情况下,简单的做法是重命名或删除旧的、有问题的公钥,并用从现有私钥生成的新的公钥替换它。

$ ssh-keygen -y -f ~/.ssh/my_key_rsa

但是,如果必须真正比较这两个部分,则使用 ssh-keygen(1) 分两步进行。首先,从已知的私钥重新生成一个新的公钥,并将其用于生成指纹到stdout。接下来,生成未知公钥的指纹以进行比较。在此示例中,比较了私钥my_key_a_rsa和公钥my_key_b_rsa.pub

$ ssh-keygen -y -f my_key_a_rsa | ssh-keygen -l -f -
$ ssh-keygen -l -f my_key_b_rsa.pub

结果是每个密钥的 base64 编码的 SHA256 校验和,其中一个指纹显示在另一个指纹下方,便于直观比较。旧版本不支持从stdin读取,因此此时将需要中间文件。更旧的版本只会显示每个密钥的 MD5 校验和。无论哪种方式,使用 shell 脚本进行自动化都足够简单,但超出了本书的范围。

验证服务器密钥

[编辑 | 编辑源代码]

首次连接时,必须对服务器的主机密钥进行可靠的验证。可能需要联系系统管理员,他们可以非带宽方式提供它,以便预先知道指纹并准备验证第一次连接。

以下是如何读取服务器的 RSA 密钥并将其指纹显示为 SHA256 base64 的示例

$ ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub     
3072 SHA256:hlPei3IXhkZmo+GBLamiiIaWbeGZMqeTXg15R42yCC0 [email protected] (RSA)

这里读取了相应的 ECDSA 密钥,但显示为 MD5 十六进制哈希

$ ssh-keygen -E md5 -lf /etc/ssh/ssh_host_ecdsa_key.pub
256 MD5:ed:d2:34:b4:93:fd:0e:eb:08:ee:b3:c4:b3:4f:28:e4 [email protected] (ECDSA)

在 6.8 之前,指纹表示为 MD5 十六进制哈希

$ ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub 
2048 MD5:e4:a0:f4:19:46:d7:a4:cc:be:ea:9b:65:a7:62:db:2c [email protected] (RSA)

也可以使用 ssh-keyscan(1) 从活动 SSH 服务器获取密钥。但是,仍然需要非带宽方式验证指纹。

警告:远程主机标识已更改!

[编辑 | 编辑源代码]

如果服务器的密钥与客户端在系统或本地帐户的authorized_keys文件中找到的记录不匹配,则客户端将发出警告,并附带可疑密钥的指纹。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:GkoIDP/d0I6KA9IQyOB9iqL+Rzpxx9LhlSJPCEfjVQ4.
Please contact your system administrator.
Add correct host key in /home/fred/.ssh/known_hosts to get rid of this message.
Offending RSA key in /home/fred/.ssh/known_hosts:19
  remove with:
  ssh-keygen -f "/home/fred/.ssh/known_hosts" -R "server.example.com"
RSA host key for server.example.com has changed and you have requested strict checking.
Host key verification failed.

警告有三个常见原因。

一个原因是服务器的密钥被替换,通常是因为服务器的操作系统被重新安装而没有备份旧密钥。另一个原因可能是系统管理员已淘汰过时的或已泄露的密钥。但是,可以更好地计划这种情况,如果时间允许,可以将新的密钥添加到服务器并让客户端使用UpdateHostKeys选项,以便在旧密钥匹配的情况下接受新密钥。第三种情况是连接到错误的机器,例如,当远程系统由于动态地址分配而更改 IP 地址时。

在所有三种情况下,密钥已更改,只有一件事要做:联系系统管理员并验证密钥。询问 OpenSSH-server 是否最近重新安装,或者机器是否从旧备份恢复?请记住,在某些情况下,系统管理员可能是您自己。

相当罕见但足够严重以至于应该确定排除的情况是,错误的机器是中间人攻击的一部分。

在所有四种情况下,可以通过任何可能验证消息完整性和来源的方法获取真实的密钥指纹,例如通过 PGP 签名的电子邮件。如果可以进行物理访问,则使用控制台获取正确的指纹。获取真实的密钥指纹后,返回到收到错误的客户端机器,从~/.ssh/known_hosts中删除旧密钥

$ ssh-keygen -R server.example.org

然后尝试登录,但首先比较密钥指纹,如果并且只有如果密钥指纹与您非带宽方式收到的指纹匹配,则继续操作。如果密钥指纹匹配,则继续登录过程,密钥将被自动添加。如果密钥指纹不匹配,立即停止并弄清楚您正在连接到的内容。最好用真正的电话,而不是电脑电话,与远程机器的系统管理员或网络管理员联系。

在 known_hosts 中,一个主机对应多个密钥,一个密钥对应多个主机

[编辑 | 编辑源代码]

可以通过使用模式匹配,或者简单地列出同一密钥的多个系统,让多个主机名或 IP 地址在known_hosts文件中使用相同的密钥。这可以在/etc/ssh/ssh_known_hosts中的全局密钥列表以及每个帐户的~/.ssh/known_hosts文件中每个帐户特定的本地密钥列表中完成。实验室、计算集群和类似的机器池可以使用这种方式使用密钥。这里是一个由三个特定主机共享的密钥,通过名称识别

server1,server2,server3 ssh-rsa AAAAB097y0yiblo97gvl...jhvlhjgluibp7y807t08mmniKjug...==

或者可以使用通配符在/etc/ssh/ssh_known_hosts~/.ssh/known_hosts中指定一个范围。

172.19.40.* ssh-rsa AAAAB097y0yiblo97gvl...jhvlhjgluibp7y807t08mmniKjug...==

相反,对于同一个地址的多个密钥,需要在 /etc/ssh/ssh_known_hosts~/.ssh/known_hosts 中为每个密钥创建多个条目。

server1 ssh-rsa AAAAB097y0yiblo97gvljh...vlhjgluibp7y807t08mmniKjug...==
server1 ssh-rsa AAAAB0liuouibl kuhlhlu...qerf1dcw16twc61c6cw1ryer4t...==
server1 ssh-rsa AAAAB568ijh68uhg63wedx...aq14rdfcvbhu865rfgbvcfrt65...==

因此,为了让服务器池共享密钥池,必须手动将每个服务器-密钥组合添加到 known_hosts 文件中。

server1 ssh-rsa AAAAB097y0yiblo97gvljh...07t8mmniKjug...==
server1 ssh-rsa AAAAB0liuouibl kuhlhlu...qerfw1ryer4t...==
server1 ssh-rsa AAAAB568ijh68uhg63wedx...aq14rvcfrt65...==

server2 ssh-rsa AAAAB097y0yiblo97gvljh...07t8mmniKjug...==
server2 ssh-rsa AAAAB0liuouibl kuhlhlu...qerfw1ryer4t...==
server2 ssh-rsa AAAAB568ijh68uhg63wedx...aq14rvcfrt65...==

虽然升级到证书可能比手动更新大量密钥更合适。

处理动态(漫游)IP 地址的另一种方法

[编辑 | 编辑源代码]

可以使用 HostKeyAlias 手动指向正确的密钥,可以作为 ssh_config(5) 的一部分,也可以作为运行时参数。在这里,机器 Foobar 的密钥用于连接到主机 192.168.11.15。

$ ssh -o StrictHostKeyChecking=accept-new \
      -o HostKeyAlias=foobar   \
      192.168.11.15

这在以下情况下非常有用:DHCP 未配置为尝试长时间为相同机器保留相同地址,或者使用某些 stdio 转发方法通过中间主机传递。

known_hosts 中的密钥更新和轮换

[编辑 | 编辑源代码]

从 OpenSSH 6.8[9] 及更高版本开始,OpenSSH 就包含了一个协议扩展,可以将弱的公钥从 known_hosts 中轮换出来。使用它,服务器能够向客户端通知其所有主机密钥,并在至少有一个已知受信任密钥的情况下,使用新的密钥更新 known_hosts。这种方法仍然需要私钥对服务器可用 [10],以便完成证明。在 ssh_config(5) 中,指令 UpdateHostKeys 指定客户端是否应该在身份验证完成后接受服务器的附加主机密钥更新,并将它们添加到 known_hosts 中。在服务器删除过时的密钥之前,可以为一段时间内提供多个相同类型的密钥,从而允许自动选项用于轮换密钥,以及从较弱的算法升级到更强的算法。有关密钥管理标准,请参见 RFC 4819:安全 Shell 公钥子系统


参考资料

[编辑 | 编辑源代码]
  1. "安全 Shell (SSH) 身份验证协议". IETF. 2006. 检索于 2015-05-06.
  2. Steve Friedl (2006-02-22). "SSH 代理转发图解". Unixwiz.net. 检索于 2013-04-27.
  3. Daniel Robbins (2002-02-01). "共同主题:OpenSSH 密钥管理,第 3 部分". IBM. 检索于 2013-04-27.
  4. Vincent Bernat (2020-04-05). "更安全的 SSH 代理转发". 检索于 2020-10-04.
  5. "管理多个 SSH 代理". 维基媒体. 检索于 2020-04-07.
  6. Damien Miller (2021-12-16). "SSH 代理限制". OpenSSH. 检索于 2022-03-06.
  7. "OpenSSH 中的基础 U2F/FIDO 支持". OpenBSD-Tech 邮件列表. 2019-11-14. 检索于 2021-03-24.
  8. Miller, Damien (2023-03-01). "为什么 ssh-keyscan 不使用 .ssh/config?". mindrot.org. https://lists.mindrot.org/pipermail/openssh-unix-dev/2023-March/040605.html. 检索于 2023-03-01. 
  9. Damien Miller (2015-02-01). "OpenSSH 6.8+ 中的密钥轮换". DJM's 个人博客. 检索于 2016-03-05.
  10. Damien Miller (2015-02-17). "主机密钥轮换,再谈". DJM's 个人博客. 检索于 2016-03-05.

 

华夏公益教科书