解决升级Openssh8.8后基于ssh-rsa算法的密钥对校验失效问题

6,463 阅读11分钟

openssh8.8默认禁止ssh-rsa加密算法

问题源自于最近无意间在工作机上升级了openssh版本(后续才发现是版本问题), 导致所有基于ssh方式的git操作全部失效, 一直提示请输入密码; 在我输入了无数次个人gitlab密码仍然失败后,第一直觉是我的ssh密钥对出了问题, 重新生成上传了新的公钥,还是同样的提示, 经过前辈的指点, 使用 ssh -vT命令查看了一下详细的日志信息, 最终发现了问题所在 在解析日志之前, 先了解一下ssh密钥登录的原理:

ssh

如果说到ssh登录, 则都会提到的三层协议

  1. 传输层协议[SSH-TRANS]:提供服务器验证、完整性和保密性功能,建立在传统的TCP/IP协议之上。
  2. 验证协议[SSH-USERAUTH]:向服务器验证客户端用户,有基于用户名密码和公钥两种验证方式,建立在传输层协议[SSH-TRANS]之上。
  3. 连接协议[SSH-CONNECT]:将加密隧道复用为若干逻辑信道。它建立在验证协议之上。

常说的ssh只是一种抽象的协议标准, 实际开发中我们使用的是开源openssh库, 该库是对ssh这一抽象协议标准的实现

ssh是基于非对称加密的一种通信加密协议,常用于做登录校验;
一般有公钥登录和口令登录两种方式: 口令登录和公钥登录

公钥登录

过程分为两个步骤。

  • 会话密钥(session key)生成
    • 客户端和服务端互相发送ssh协议版本以及openssh版本, 并约定协议版本
    • 客户端和服务端互相发送支持的加密算法并约定使用的算法类型
    • 服务端生成非对称密钥,并将公钥以及公钥指纹发送到客户端
    • 客户端和服务端分别使用DH算法计算出获取会话密钥,后续所有流程都会使用会话密钥加密传输
  • 认证
    • 客户端将公钥指纹信息 使用上述的q(会话密钥)加密发送到服务端
    • 服务端拿到后解密, 并去authorized_keys中匹配对应的公钥
    • 服务器生成随机数 x,并用该公钥加密后生成结果 S(x),发送给客户端
    • 客户端使用私钥解密 S(x) 得到 x
    • 客户端将解密出的随机数用会话密钥加密,将得到的值进行MD5运算 n(q+x)。然后将这个值发送给服务器
    • 服务器端对原始随机数也使用会话密钥加密后计算MD5 m(q+x)
    • 服务器比较 m(q+x) 和 n(q+x),两者相同则认证成功

在认证阶段, 由于ssh是将所有用户的公钥保存在authorized_keys, 那么是如何匹配本次链接请求组使用的公钥呢?

答案是在认证阶段, 会传递本机公钥的指纹信息, 服务器端根据指纹信息比对出是否存在对应的客户端公钥, 后续会使用该公钥进行身份认证

由于ssh是基于TCP实现的, 所以可以使用wireshark抓包观察数据交互过程

ssh_wireshark.jpg

  1. (88~90)是tcp连接的三次握手
  2. (91~93)是协议版本约定
  3. (98~99)是加密算法的约定
  4. (102~105)是DH算法生成会话密钥, 以及约定使用会话密钥加密后续通讯
  5. 后续认证阶段由于都是加密通讯, 所以无法直观分析, 感兴趣的同学可以查看openssh的源码实现

口令登录:

  • 会话密钥生成(相同)
  • 认证: 在上一步会话密钥生成后, 客户端使用该密钥对密码进行加密, 传输给服务器服务器使用该密钥解密获取到密码, 进行校验

以上是ssh协议的原理,我们了解到在验证阶段会用到客户端的公钥, openssh作为实现会判断公钥生成算法类型, 由于实现中不再支持ssh-rsa, 会尝试进行另一种允许的口令登录方式, 具体可以通过日志查看

日志解析 & 解决方案

ssh -vT git@gitb.xxxx.com

# 日志如下
# 版本信息
OpenSSH_8.8p1, OpenSSL 1.1.1m  14 Dec 2021 
# 读取配置文件
debug1: Reading configuration data /Users/clownfish/.ssh/config
debug1: Reading configuration data /usr/local/etc/ssh/ssh_config
...
# 查找身份文件, 成功返回0, 失败返回-1, 由于本地只有默认的id_ras 所以只有这一项返回0
debug1: identity file /Users/clownfish/.ssh/id_rsa type 0
debug1: identity file /Users/clownfish/.ssh/id_rsa-cert type -1
...

# 版本号
debug1: Local version string SSH-2.0-OpenSSH_8.8
debug1: Remote protocol version 2.0, remote software version OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.8
...
# 查找到host
debug1: Found key in /Users/clownfish/.ssh/known_hosts:5

debug1: Will attempt key: /Users/clownfish/.ssh/id_rsa RSA SHA256:7KTOiN2jUDgc5SJm22GnEk5TpshjTBk/lU9stwJYx48
... # Will attempt key 尝试其他类型密钥

# 认证支持两种方式, 公钥和口令
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Offering public key: /Users/clownfish/.ssh/id_rsa RSA SHA256:7KTOiN2jUDgc5SJm22GnEk5TpshjTBk/lU9stwJYx48
# 针对上文找到的public key 没有相互支持的签名算法
debug1: send_pubkey_test: no mutual signature algorithm
... # Trying private key 尝试其他私有key
# 尝试口令登录
debug1: Next authentication method: password
... # 一直提示输入密码

找到关键字no mutual signature supported.去查了一下发现是openssh8.8版本问题不再支持ssh-rsa, (openssh 8.8 release notes中说明默认会自动转换),
但是链接到版本较低的server时,还是要手工处理 从日志中可以看到我们server的版本是6.6

那么解决办法也就有了, 要么重新生成其他算法的秘钥对上传, 要么修改配置再次开启支持, 这里只针对第二种 在config中做如下配置:

Host * # 第一行说明对所有主机生效
  PubkeyAcceptedKeyTypes=+ssh-rsa # 第二行是将ssh-rsa加会允许使用的范围, 没配置会提示no mutual signature supported.表示找不到匹配的签名算法
  # HostKeyAlgorithms +ssh-rsa # 第三行是指定所有主机使用的都是ssh-rsa算法的key, 我个人测试可以不写,如果仍不生效可以打开测试

再次测试发现可以正常登录

另外开局提到的,git认证提示输入的密码,其实应该是登录服务器git用户的密码,而不是指的gitlab中的个人账号密码;因为git使用ssh目的仅仅是登录校验,不用于访问数据,由于个人对server端了解的较少, 所以在这里也坑了很久, 希望了解的同学多多指教

扩展

ssh know_hosts 防止中间人攻击

ssh基于非对称加密方式,亟需解决的一个问题就是防止中间人攻击, https中是基于CA认证实现的, ssh则是基于指纹认证; 即在首次发起链接时,会提示以下内容:

The authenticity of host 'xxxxxx' can't be established.
RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.
Are you sure you want to continue connecting (yes/no)? 

在输入yes后,提示

Warning: Permanently added 'xxxx' (RSA) to the list of known hosts. 

konw_hosts文件存储在~/.ssh/konw_hosts, 里面记录了确认后的主机host和对应的加密算法, 以及对应的公钥信息, 后续再次登录主机时,则不会再提示以上can't be established的内容

RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d. 这个是公钥指纹, 由于公钥信息太长, 这里对公钥做了一个摘要,叫做指纹信息,

ssh config 常用参数

ssh有三种方式来配置,分别是命令行参数, 用户配置文件, 和系统配置文件, 优先级是命令行参数 > user config > system config

  • 命令行参数,如-p 222, -i /path/to/identity_file 等选项来设置SSH的端口号或认证证书位置
  • 用户的配置, 路径为~/.ssh/config 默认是不存在,手动创建即可
  • 系统配置, 路径为/etc/ssh/ssh_config

配置文件用来针对ssh一些参数做配置, 例如指定HostHostName、Port、User、IdentityFile(认证证书文件, 绝对路径), 更多参数配置使用man ssh_config可以查看

以下是常用的一些配置参数:

  • ssh config 文件中的一些常用变量意义:
    • %d,本地用户目录 ~
    • %u,本地用户
    • %l,本地主机名
    • %h,远程主机名
    • %r,远程用户名
  • 禁用密码登录: 如果你对服务器安全要求很高,那么禁用密码登录是必须的。因为使用密码登录服务器容易受到暴力破解的攻击,有一定的安全隐患。那么你需要编辑服务器的系统配置文件 /etc/ssh/sshd_config
    PasswordAuthentication no
    ChallengeResponseAuthentication no
    
  • 远程服务当本地用: 通过 LocalForward 将本地端口上的数据流量通过 ssh 转发到远程主机的指定端口。感觉你是使用的本地服务,其实你使用的远程服务。如远程服务器上运行着 MySQL,端口 3306(未暴露端口给外部)。那么,你可以:
    Host db
        HostName db.example.com
        LocalForward 3306 localhost:3306
    
  • 多连接共享: 在你打开多个 shell 窗口时需要连接同一台服务器,如果你不想每次都输入用户名,密码,或是等待连接建立,那么你需要添加如下配置到 `~/.ssh/config
    ControlMaster auto
    ControlPath /tmp/%r@%h:%p
    
  • 代理登录: 有的时候你可能没法直接登录到某台服务器,而需要使用一台中间服务器进行中转,如公司内网服务器。首先确保你已经为服务器配置了公钥访问,并开启了agent forwarding,那么你需要添加如下配置到 ~/.ssh/config
    Host gateway
        HostName proxy.example.com
        User root
    Host db
        HostName db.internal.example.com                  # 目标服务器地址
        User root                                         # 用户名
        # IdentityFile ~/.ssh/id_ecdsa                    # 认证文件
        ProxyCommand ssh gateway netcat -q 600 %h %p      # 代理命令
    

git ssh的原理

告知git获取私钥:git使用ssh-agent来进行ssh协议传输,默认情况下ssh-agent会读取~/.ssh目录下的id_rsa或id_dsa文件。

告知git服务器获取公钥:这一步对于大部分git用户是不需要知道的,只需把公钥给git管理员即可。

大部分git服务器提供的SSH访问方式如下:在主机上建立一个 git 账户,让每个需要写权限的人发送一个 SSH 公钥,然后将其加入 git 账户的 ~/.ssh/authorized_keys 文件。 这样一来,所有人都将通过 git 账户访问主机。所以我们在使用ssh访问git时, 请求连接都是git@gitlab.xxx.com 以git作为用户访问

git ssh的多账号访问方式

我们在创建ssh公私钥时, 默认都是id_rsa, 可以指定新的公私钥文件名,以防止覆盖已有的id_rsa文件, 在Enter file in which to save the key步骤时指定完整的文件路径

ssh-keygen -t rsa -C "xxx@gmail.com"                             
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/helloworld/.ssh/id_rsa): /Users/helloworld/.ssh/test_rsa

在gitlab/github 仓库中设置test_rsa对应的公钥信息后, 由于ssh默认是查询id_rsa私钥做登录认证, 所以需要指定host使用哪个私钥进行校验

根据上面对ssh config的理解, 有三种方式可以设置:

  • 一种是通过ssh -i参数指定identity_file认证文件, 但是git命令是底层调用的ssh, 没办法传递-i参数到ssh,
  • 第二种在config中配置host和私钥登录的对应关系(这里最好是设置在user config), 示例如下:
Host github.com
HostName github.com
Port 22
User git
IdentityFile ~/.ssh/test_rsa

配置好以后, 再次使用ssh做github登录验证时, 则使用本地的test_rsa私钥做校验, 现在就可以同时使用多个ssh密钥对登录不同的git仓库

总结

工作中经常需要用到ssh作为git的登录校验方式, 所以花时间学习了下大概原理, 不过由于是个初学者, 所以很多地方可能存在漏洞或者错误, 所以如果您发现了问题, 请联系我多多指教, 不胜感激.
另外欢迎加好友讨论各种iOS问题, 互相学习

openssh release notes
gitlab Docs
ssh配置文件详解
ssh 双钥认证原理
使用wireshark分析ssh口令登录细节
密钥登录原理篇
ssh PublicKey fingerprint
ssh RFC 中文版