当tcp连接建立后,双方都要发送identification string,格式为:
SSH-protoversion-softwareversion SP comments CR LF
在发送完identification string之后,会进行 key exchange。
兼容性:
在ssh2.0之前的版本没有正式的文档。
old client, new server:
服务端应该有一个兼容选项的配置项,如果配置了,那么服务端发送的protoversion是1.99。对支持ssh2.0的客户端来说,1.99应该等于2.0。
new client, old server:
了解到服务端版本比较老之后,客户端应该重新建立连接,使用低版本信息。
binary packet:
uint32 packet_length
byte padding_length
byte[n1] payload; n1 = packet_length - padding_length - 1
byte[n2] random padding; n2 = padding_length
byte[m] mac (Message Authentication Code - MAC); m = mac_length
mac = MAC(key, sequence_number || unencrypted_packet)
sequence_number不传输,第一个包为0,以后每收到一个包,递增1。
各种密钥协商的过程:
client: 发送identity string,发送KEX_INIT包,其中包含COOKIE和支持的各种算法。
server: 收到identiy string,发送KEX_INIT包,其中包含COOKIE和支持的各种算法。
至此,双方知道所使用的算法。
使用serverVersion, clientVersion, 两个KEX_INIT包,初始化Kex。
client: 发送KEXDH_INIT或KEX_DH_GEX_REQUEST。
server: 发送KEXDH_REPLY或KEX_DH_GEX_GROUP,将DH的P和G发送给客户端。
client: 发送DH_GEX_INIT,其中包含客户端的DH公钥。
server: 发送DH_GEX_REPLY,其中包含公钥(HostKey),自己的DH公钥,以及签名算法和已知信息的签名。
密钥的计算:
client: 客户端发送NEWKEYS消息。
server:
已知信息有:
V_C: 客户端的初始报文(包含版本信息的报文)。
V_S: 服务端的初始报文。
I_C:客户端的SSH_MSG_KEX_INIT的有效负荷。
I_S: 服务端的SSH_MSG_KEX_INIT的有效负荷。
e: 客户端DH公钥。
f: 服务端DH公钥。
K: DH计算的结果。
H=hash(V_C||V_S||I_C||I_S||K_S||e||f||K);
会话第一次秘钥交换生成的H才是session_id。
密钥计算公式:hash(K, H, 单个字符,session_id)
其中单个字符可以是如下几个字符:
'A': 用于计算客户端到服务端的IV。
'B': 用于计算服务端到客户端的IV。
'C': 用于计算客户端到服务端的加密秘钥。
'D': 用于计算服务端到客户端的加密秘钥。
'E': 用于计算客户端到服务端的HMAC秘钥。
'F': 用于计算服务端到客户端的HMAC秘钥。
计算的结果记为RE,如果想要的秘钥长度比RE长,RE=RE+hash(K, H, RE)。如果还不够,继续计算添加。