SSH 密钥管理

2016-07-26 linux security ssh

服务端会保存自己的公私钥,客户端每次链接时会确认是否为上次记录主机。

简介

通过 SSH 远程链接到新的主机时,会有如下的提示

$ ssh 127.0.0.1
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:QUfCwW6Br5EwwESsulN2TEidBoDNca888RNflZG++bI.
Are you sure you want to continue connecting (yes/no)? yes

当输入 yes 确认之后,会将服务端的公钥添加到 ~/.ssh/known_hosts 文件里,此外,系统也有一个这样的文件,通常为 /etc/ssh/ssh_known_hosts 保存一些对所有用户都可信赖的远程主机的公钥。

中间人攻击

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

实际上在真正实施时会存在中间人攻击 (Man-in-the-middle Attack),简单来说:如果有人截获了登录请求,然后冒充远程主机,将伪造的公钥发给用户,因为 SSH 中的公钥没有类似 HTTPS 的证书中心,就无法判断公钥的合法性,这样攻击者就可能会获取到用户的密码。

SSH解决方案

关键就是,如何对 Server 的公钥进行认证,在 HTTPS 中是通过证书认证,而 SSH 的公私钥都是自己生成的,每个客户端只能自己确认。也就是说,对于主机合法性的判断完全交给了用户,所以,首次登录就会出现如上的提示。

也就是说,ssh 客户端会将远端主机的公钥指纹打印出来,由用户自己判断是否要信任这个公钥或者主机。所谓的 “公钥指纹” 实际上,实际上是对公钥的 MD5 或者 SHA256 值。

那么,用户如何知道远程主机的公钥指纹应该是多少?SSH 没有给出具体的方案,一般远程主机可以在自己的网站上贴出公钥指纹,以便用户自行核对,也可以自己集中管理。

假设用户经过风险衡量后,决定接受这个远程主机公钥,那么会再次提示如下内容,表示 host 主机已经得到认可。

Warning: Permanently added 'host,12.18.429.21' (RSA) to the list of known hosts.

然后,会要求输入登录密码,在密码验证通过后,远程主机的公钥会被保存在文件 $HOME/.ssh/known_hosts 之中,下次系统就会认出它的公钥已经保存在本地了,从而跳过警告部分,直接提示输入密码。

配置

使用时 OpenSSH 默认会创建证书信息,包括了服务端和客户端。

服务端

在交互过程中,会采用非对称加密完成,例如 RSA、DSA、ECDSA 等算法,其中公钥会分发给各个客户端,而私钥会保存在服务端,通常不建议共享。

对于 OpenSSH 来说,会保存在 /etc/ssh 目录下,一般为 ssh_host_<rsa/dsa/ecdsa>_key (路径可以在配置文件中进行修改) ,通常是在服务启动的时候自动生成,也可以通过 ssh-keygen 手动生成。

当客户端连接到服务器之后,会将服务端的 Key 保存为 Known Host ,一般保存在 /etc/ssh/known_hosts 以及 ~/.ssh/known_hosts 文件中。

默认 Host Key 只有 root 用户可读,但是,如果有人获取到了 Host Key ,那么就可以发起中间人攻击,为此,有些 SSH 服务提供了配置中心,用于集中管理。

客户端

如上,已经经过验证的服务端信息会保存在 ~/.ssh/known_hosts 文件中,默认会生成类似如下内容的信息。

127.0.0.1 ecdsa-sha2-nistp256 AAAA....=

包括了主机、相关算法以及主机公钥信息,如果开启了 HashKnownHosts yes 配置,就可以看到有如下内容。

|1|c2UrTftB8P0W1m+YyxnTYo+Th0M=|tw98264YL5dxT7v6EiEXfcTlUc8= ecdsa-sha2-nistp256 AAAA....=

主机名以哈希加密形式存储,这样可以隐藏主机名称和IP地址对外暴露, 哈希过的主机名以 | 字符开头,每一单行上只能允许一个哈希主机名出现。

常用命令

通过 ssh-keyscan 命令可以获取服务器公钥,一般会根据不同的加密方法有多个密钥。

----- 如果不指定类型会打印所有服务端支持类型,类型有ecdsa、rsa、ed25519
$ ssh-keyscan -t ecdsa -p 22 127.0.0.1
$ ssh-keyscan -t ecdsa -p 22 127.0.0.1 2>/dev/null | ssh-keygen -E sha256 -lf -

对于 ssh-keygen 命令,除了可以生成公私钥之外,还可以通过 -l 查看,并通过 -E 指定类型参数。

$ ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -C "foobar@kidding.com"
参数:
    -t:   加密类型
    -f:   指定输出文件
    -N:   通过该密码加密私钥,为空则会自动登陆
    -C:   注释,通常为邮箱名称,会保存在公钥中

查看本地保存公钥的 SHA256 签名,也可以通过 -E md5 查看对应的 MD5 值。

$ ssh-keygen -E sha256 -lf ~/.ssh/known_hosts
$ ssh-keygen -E sha256 -lf /etc/ssh/ssh_host_ecdsa_key.pub

常见问题

登陆禁止

有时候需要 SSH 登陆到别的 Linux 主机上去时,可能会弹出如下类似提示:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
... ...
Host key verification failed.

在客户端第一次使用 SSH 连接时,会把访问过的计算机公钥记录在 ~/.ssh/known_hosts 文件中,下次访问时,OpenSSH 会核对公钥,如果公钥不同,则会发出警告,避免受到 DNS Hijack 之类的攻击。

可以通过 ssh-keygen -l -f ~/.ssh/known_hosts 命令查看,但是如果主机重装或者重新生成了公钥,那么就会出现如上的报错,可以通过如下方式解决:

  • 通过 ssh-keygen -f "~/.ssh/known_hosts -R HOST:PORT 手动删除修改 known_hosts 里相应的记录;
  • 修改配置文件 ~/.ssh/config 添加 StrictHostKeyChecking noUserKnownHostsFile /dev/null 即可。

注意第二种安全性比较低。