双向身份认证
在进行 ssh 加密通讯的过程中,避免不了去接触公钥私钥这样的密钥对。
- 如果你在使用服务器的过程中重装过系统,可能你也会知道
~/.ssh/known_hosts这个文件。 - 如果你配置了密钥登录服务器,可能你会知道
~/.ssh/authorized_keys这个文件。
当登录一个服务器的时候,需要先后两步:
- 先验证服务器的身份(确认服务器是真实的,而非中间人伪造)
- 再向服务器证明自己的身份(证明自己有权限登录)
你可能知道配置密钥登录服务器,是配置了一对密钥,公钥在服务器上,私钥在本地,本质是验证登录的权限。那么这其实就是第二步,你向服务器去证明自己的身份。
实际上,当你第一次登录服务器或者重装系统后登录服务器,会提示你验证服务器公钥指纹,当你确定后,会将这个公钥写到 ~/.ssh/known_hosts 这个文件中。这其实是服务器中的密钥对,当你拿到公钥,服务器上有私钥,此时就可以去验证服务器的身份了。
客户端必须先通过服务器的身份认证(先确认对方是真实服务器),才会发起用户身份认证(再证明自己有权登录);反之,若服务器身份存疑(如公钥不匹配),客户端会直接终止连接,不会进行后续的用户认证。
两者共同确保了 “双向可信”:用户信任服务器是真实的,服务器信任用户是有权限的,在此基础上才能建立安全的加密通信。
这就是 SSH 最核心的安全设计 ——双向身份认证:
- 服务器通过 “公钥给客户端、私钥自己留存” 证明自己的真实性。
- 用户通过 “私钥在本地、公钥在服务器” 证明自己的权限。
known_hosts 和 authorized_keys 分别是这两个过程的 “凭证存储文件”:
- 服务器公钥存储在用户端的
known_hosts文件中。(用户验服务器) - 用户端公钥存储在服务器的
authorized_keys文件中。(服务器验用户)
公钥指纹
基于 SSH 的双向身份认证,一般会先验证服务器的身份,再向服务器证明自己的身份。
验证服务器身份的方式其实也是通过密钥,这个密钥是服务器上的,将服务器上的密钥对中的公钥给到用户,然后进行验证。在用户第一次请求服务器公钥的时候,通常会碰到如下界面:
![图片[1]-从 known_hosts 到 authorized_keys:ssh 双向身份认证深度解析 - 渔网札记-渔网札记](https://blog.image.lilwhale.cn/wp-content/uploads/2025/06/20250718115454476.png)
本质就是问你是否信任这个公钥指纹,这其实就是服务器中通过 ED25519 算法生成的密钥对中的公钥,不过这里显示的字符串是这个公钥的指纹。
一般情况下,我们都会无脑输入 yes,然后这个公钥就会存入到本地 ~/.ssh/known_hosts 文件当中,后续登录遍不会再询问,而是直接用这里面存储的公钥进行服务器身份的验证。
但是如果这个时候有一个人,他截获了我们获取服务器公钥的请求,然后他去请求了服务器的公钥,在他拿到公钥后,用他自己的公钥给我们,我们还以为这就是服务器的公钥。至此以后,我们跟服务器之间的会话通讯都会经过他。这就是著名的中间人攻击(Man-in-the-middle attack)。
中间人攻击的解决方案:将发送过来的公钥指纹和服务器中的公钥进行比对。
公钥指纹的意义:使用 ssh-keygen 工具将服务器的公钥转换成指纹,就可以直观且方便的对比。
站在公司员工的场景来看:
一般只能通过寻求管理员将服务器的公钥文件发给你,然后进行对比,最好的情况是管理员直接发给你公钥指纹。
![图片[2]-从 known_hosts 到 authorized_keys:ssh 双向身份认证深度解析 - 渔网札记-渔网札记](https://blog.image.lilwhale.cn/wp-content/uploads/2025/06/20250718123149791.png)
而站在管理员的场景来看:
我要不就登录服务器去下载文件,或者登录服务器将公钥使用 ssh-keygen -lf 拿到公钥指纹给员工怎样都要登录服务器。
实际上可以借助 ssh-keyscan 这个工具直接拿到服务器的公钥信息,再通过拿到的公钥用 ssh-keygen 计算出公钥指纹,全程不用登录服务器,但是需要服务器的 IP。
![图片[3]-从 known_hosts 到 authorized_keys:ssh 双向身份认证深度解析 - 渔网札记-渔网札记](https://blog.image.lilwhale.cn/wp-content/uploads/2025/06/20250718143051887.png)
首先通过 ssh-keyscan server_ip 拿到服务器上的所有主机公钥,然后将拿到的内容通过管道输送给 ssh-keygen -lf 进行处理,但是这个 -lf 选项是从文件中读取内容计算指纹的,而通过 ssh-keyscan 拿到的内容原本是输出在显示器上的,我们将其通过管道输送给 ssh-keyscan 作为它的输入,让它进行处理。故此在后面跟上一个连字符 -,它的意思是不要从文件读取了,从标准输入中读取内容。
整条命令 ssh-keyscan server_ip | ssh-keygen -lf - 的执行流程如下:
ssh-keyscan首先运行,成功获取到服务器的所有主机公钥。server_ipssh-keyscan本来打算把这些公钥文本打印到你的屏幕上,但管道|拦截了这一切。- 管道
|把这些公钥文本,原封不动地传送给了ssh-keygen命令。 ssh-keygen -lf -命令启动,它被告知要计算指纹(-l),并且从标准输入(-)而不是具体文件(-f)中读取公钥数据。ssh-keygen接收到从管道传来的公钥文本,逐一计算出它们的指纹,并最终将这些指纹打印到你的屏幕上。
加密算法协商
确定接受指纹后,会将多个公钥写到本地 ~/.ssh/known_hosts 文件中,而非一个公钥。而这些公钥是以不同的算法生成的密钥,命名格式基本都是 ssh_host_*_key 的形式。
![图片[4]-从 known_hosts 到 authorized_keys:ssh 双向身份认证深度解析 - 渔网札记-渔网札记](https://blog.image.lilwhale.cn/wp-content/uploads/2025/06/20250717152847905.png)
既然如此,这些密钥的目的很明确,就是要告诉你,我这个密钥匙是哪一个算法生成的。
服务器就像一个外交官,我掌握多国的语言,然后我会判断你说的哪一种语言来和你去进行交流,同样你也可能会多国语言,但是我一定会选择你的母语来跟你进行交流,这是最优的选择。
当你去访问服务器的时候,你会告诉服务器我都支持哪些算法,而服务器会选择一个相对双方都最好的算法(通常是最安全)来对本次会话进行加密。
服务器保留着所有这些不同类型的密钥,是为了确保无论客户端支持哪种算法,总有一款能匹配上,并且尽可能地使用最安全的那一种。
ssh_host_ed25519_key: 最现代、最高效、最推荐的密钥类型。ssh_host_ecdsa_key: 基于椭圆曲线的密钥,比 RSA 安全,但速度可能不如 Ed25519。ssh_host_rsa_key: 最传统、兼容性最好的密钥,为了照顾一些非常老旧的客户端。
这就是所谓的加密算法协商 (Algorithm Negotiation)。
我不禁有一个疑惑。既然协商后只用一个密钥,为什么要把服务器所有的公钥都保存到本地?
事实上,老版本的 SSH 客户端正是如我想的这样工作的,它只保存协商成功的那一个密钥。
这就导致,如果服务器端的管理员出于某种原因禁止掉了这个跟你通讯的算法,换成了其他的加密算法,就会出现严重的问题。
故此,后来就会在第一次登录服务器的时候,把服务器所有支持的算法生成的公钥保存到本地。这样就算服务器禁止掉了其中一个加密算法,还可以平滑过渡到其他的加密算法。
这就是现代 OpenSSH 客户端(大约从 6.8 版本开始)所做的事情:
- 获取所有公钥: 在初次连接的密钥交换阶段,客户端不仅接收用于协商的那个公钥,还会请求服务器“请把你所有有效的主机公钥都告诉我”。
- 用户验证一个: 客户端会选择一个最优先的公钥(比如
ed25519)生成指纹,并呈现给你进行人工确认。 - 保存所有公钥: 当你输入
yes确认后,客户端会将从服务器收到的所有类型的公钥(ed25519,ecdsa,rsa等)都记录到你的~/.ssh/known_hosts文件中。
你可以打开你的 文件看一眼,对于同一个IP或主机名,你很可能会看到类似下面这样的三行记录:~/.ssh/known_hosts
116.205.170.29 ssh-rsa AAAA...
116.205.170.29 ecdsa-sha2-nistp256 AAAA...
116.205.170.29 ssh-ed25519 AAAA...
故此,客户端之所以保存所有公钥,是一种非常智能的“预缓存”行为,它用首次连接时的一次确认,换取了未来在服务器算法升级或变更时连接的健壮性和连续性。

暂无评论内容