SSH是一个古老的协议,最新的SSH2.0发布于2006年1月,已经近20年没有更新了(除了添加了新的加密算法)。
SSH的安全性设计中规中矩,握手过程相当繁锁,一个报文只做一件事,协商一个参数,还要反复发送确认。
这是华为知识百科上的握手过程,但这个是不完整的,把非关键的过程省略了,可能因为画全了太繁锁。
握手
下面是握手步聚:
1. TCP三次握手
一共3个报文
2. 协商协议版本号
一共2个报文(虽然图上是3个报文,但我抓包只有2个报文,没有确定版本协商的报文)
3. 算法协商
一共2个报文,是在Key Exchange Init消息中完成的,双方各种发送自己支持的算法列表。然后在列表中从前到后选择第1个双方都支持的算法。这里发送方和接收方应该是可以选择不同的算法,用这个选择法根本没法保证选择的算法一致。
参考RFC4253第7章,算法协商是按如下规则进行的:
对于加密和MAC算法在每个方向上是可以不同的,可以选择双方各自的首选算法作为对应方向的加密和MAC算法,对于AEAD加密,MAC算法协商是忽略的。
密钥交换算法因为必须双方一致才能进行,所以优先以客户端首选算法为准。
服务器公钥签名算法只要双方都支持就行,DH KE Reply报文中会返回一个具体的服务器签名算法/公钥算法及其公钥和签名结果。服务器上通常会有多个公钥,这些公钥保存在/etc/ssh/*.pub
4. 密钥交换
对于ECDH/ED25519交换来说一共需要3个报文,传统DH算法则需要6个报文,两个用于交换公钥和随机数,最后还有一个New Keys报文用于确认交换成功(如果算法协商时没有协商一致的交换算法就会交换失败)。
5. 认证
对于公钥认证是2个报文,客户端用私钥对公钥以及前面的报文,用户身份信息等进行签名。服务器用公钥对签名进行验证,认证通过返回认证成功的报文。
6. 会话请求
2个报文,用于开始会话数据传输,下面就可以命令交互了。
以上一共用掉了17个报文了。
rekey,重协商,PFS
RFC4253建议每个小时或传输1GB的数据就要重新握手来实现PFS,重新握手包含了算法协商和密钥交换两个步聚共5个报文。握手是在Transport层进行的,而且是加密的。
RFC4253并没有说明如何在rekey的过程中不丢包,但要实现不丢包只有采取下述办法:
– 所有数据包按顺序到达,这个由TCP来保证
– 收到对方的New Keys报文后只切换这个方向的密钥,也就是两个方向的密钥不是同时切换的。
OpenSSH中是通过RekeyLimit参数配置rekey的,但是默认没有开启。
服务器公钥签名
服务器公钥是保存在/etc/ssh/目录的,用服务器私钥对服务器公钥和服务器标识信息(可能就是IP地址)进行签名,然后客户端用服务器的公钥进行验签。服务器的公钥是和签名一起在DH KE Reply报文中发送过来的,用户需要自行判断是否信息服务器的公钥。信任的服务器公钥保存在~/.ssh/known_hosts文件中。
服务器公钥签名是与密钥交换一起进行的,不需要额外的数据包。
有些SSH客户端会要求提前信息SSH服务器公钥,比如github就是如此,而且github公示了它的公钥信息以方便进行信息。
以下是github的公钥信息
https://docs.github.com/zh/enterprise-cloud@latest/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
自己的公钥指纹可以用以下命令查看:
ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub -E sha256
对服务器公钥进行信任可以避免中间人攻击。
参考:
http://walkerdu.com/2019/10/24/ssh/
https://segmentfault.com/a/1190000011395818
https://datatracker.ietf.org/doc/rfc4253/
https://datatracker.ietf.org/doc/rfc4251/
https://datatracker.ietf.org/doc/rfc4252/
https://datatracker.ietf.org/doc/rfc4344/
https://datatracker.ietf.org/doc/rfc5656/
https://datatracker.ietf.org/doc/html/rfc8731
https://datatracker.ietf.org/doc/rfc4419/