SSH 通讯是基于几个基本协议完成,这里简单介绍。
简介
其中 SSH 协议框架中最主要的部分是三个协议:
- 传输层协议 Transport Layer Protocol,提供服务器认证、数据加密、信息完整性等特性;
- 用户认证协议 User Authentication Protocol,为服务器提供客户端的身份认证;
- 连接协议 Connection Protocol,将加密隧道复用成若干个逻辑通道,提供给更高层的应用协议使用。
每个链接可以创建多个 Channel,Channel 可以是 Session,端口、Agent 转发等等,每个 Session 可以执行多种类型的操作,例如会话、SCP、SFTP 等等。
主机密钥机制
由于SSH协议是面向互联网网络中主机之间的互访与信息交换,所以主机密钥成为基本的密钥机制。也就是说,SSH协议要求每一个使用本协议的主机都必须至少有一个自己的主机密钥对,服务方通过对客户方主机密钥的认证之后,才能允许其连接请求。一个主机可以使用多个密钥,针对不同的密钥算法而拥有不同的密钥,但是至少有一种是必备的,即通过 DSS算法产生的密钥。关于DSS算法,请参考[FIPS-186]。
协议
详细的标准可以参考 OpenSSH Specifications 中 SSH protocol version 2 Core RFCs 相关协议的介绍。
Client Server
| |
|<------------------- TCP Conn ------------------->|
| |
|------------- SSH-2.0/1.5-XXX_1.2\r\n ----------->| 1.5对应SSHv1,2.0对应SSHv2,后面是应用信息+版本
|<--------- SSH-2.0-XXX_8.4 Debain-5-\r\n ---------| 返回服务端版本
| |
|------------ Key Exchange Init(20) -------------->| 服务端和客户端并发执行
|<----------- Key Exchange Init(20) ---------------|
| |
|---- Diffie-Hellman Key Exchange Init(30/34) ---->| RFC4419/9142
|<---- Diffie-Hellman Key Exchange Reply(31) ------|
|---------- Diffie-Hellman GEX Init(32) ---------->|
|<--------- Diffie-Hellman GEX Reply(33) ----------|
| |
|<-------- New Keys, Encrypted Packet(21) ---------|
|--------- New Keys, Encrypted Packet(21) -------->|
| packet length |
| | padding length |
+--------------------+--------------------+-------------+----------------+-----+
| packet length(u32) | padding length(u8) | payload | random padding | mac |
+--------------------+--------------------+-------------+----------------+-----+
* random padding 任意长度的填充
第二步,互相发送Key Exchange Init数据包,进行算法协商;数据包内容格式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
byte SSH_MSG_KEXINIT
byte[16] cookie (random bytes)
name-list kex_algorithms
name-list server_host_key_algorithms
name-list encryption_algorithms_client_to_server
name-list encryption_algorithms_server_to_client
name-list mac_algorithms_client_to_server
name-list mac_algorithms_server_to_client
name-list compression_algorithms_client_to_server
name-list compression_algorithms_server_to_client
name-list languages_client_to_server
name-list languages_server_to_client
boolean first_kex_packet_follows
uint32 0(为将来扩展预留)
* cookie 发送方生成的随机值,作用是使任何一方都不可能对密钥和会话标识符拥有完全决定权。
* kex_algorithms 密钥交换算法。
* server_host_key_algorithms 受支持的为服务器主机密钥服务的算法的名称列表,按优先级排序。
encryption_algorithms: 可接受的对称加密算法(也称为加密器)的名称列表,按优先级排序。
mac_algorithms: 可接受的 MAC 算法的名称列表,按优先级排序。
compression_algorithms: 可接受的压缩算法的名称列表,按优先级排序。
languages: 语言标志的名称列表,按优先级排序。
first_kex_packet_follows: 表明是否有一个猜测的密钥交换数据包跟随。
其中 name-list
中的算法字符串列表通过 ,
分割,优先级高的算法排在前面,在协商过程中会循环遍历 Client 支持的算法,当在 Server 端找到相同的算法时,就使用该算法。
在协商过程中包含了如下四类算法:
Key EXchange algorithms, KEX
key host-key algorithms
mac message authentication code algorithms
enc encryption algorithms (ciphers)
----- 查看当前客户端支持的相关算法
$ ssh -Q kex
----- 打印服务端的配置,如下过滤交换算法
# ssh -T | grep -w kexalgorithms
工作过程
为了建立一个 SSH 安全连接,服务器端与客户端要经历如下五个阶段。
- 版本号协商阶段,目前包括 SSH1 和 SSH2 两个版本,双方通过版本协商确定使用的版本;
- 密钥和算法协商阶段,支持多种加密算法,双方需要根据本端和对端支持的算法,协商出本次通讯最终使用的算法;
- 认证阶段,客户端向服务器端发起认证请求,服务器端对客户端进行认证;
- 会话请求阶段,认证通过后,客户端向服务器端发送会话请求;
- 交互会话阶段,会话请求通过后,服务器端和客户端进行信息的交互。
1. 版本号协商阶段
这一阶段的都是采用明文方式传输,服务端会监听 22 端口,客户端会主动建立连接,然后会经历如下的协商过程:
- 服务端向客户端发送第一个请求,是一个字符串 “SSH-<主协议版本号>.<次协议版本号>-<软件版本号>",其中软件版本号主要是为了进行调试;
- 客户端解析,如果服务端版本号低,且支持低版本,那么使用服务器低版本协议号,否则使用自己的版本号;
- 客户端响应一个报文,包含了客户端决定使用的版本号;
- 服务端根据接收到的报文,判断是否可以继续进行工作。
这一阶段,主要是由于 SSH 的多版本引起的,用来协商使用的 SSH 版本号。
2. 密钥和算法协商阶段
正常来说,秘钥交换要滞后于算法协商,而在 SSH 中两者是同步进行的,这也是最为关键的步骤,用来进行
- 服务端和客户端分别发送协商报文给对端,包括了自己支持的公钥算法、加密算法、Message Authentication Code, MAC 消息验证码算法列表、压缩算法列表等;
- 服务端和客户端分别根据对端和本端的算法列表得出最终使用的算法;
- 服务端和客户端利用 Diffie-Hellman Exchange, DH 交换算法、主机密钥对等参数,生成会话密钥和会话 ID。
在上述的第 <2>
步中,双方会尝试选择第一个算法,如果相同则强制使用该算法。
通过以上步骤,服务端和客户端就协商得到了相同的会话密钥和会话 ID 。
- 对于后续传输的数据,两端都会使用会话密钥进行加密和解密,保证了数据传送的安全;
- 在认证阶段,两端会使用会话 ID 用于认证过程。
注意,在协商阶段之前,服务器端已经生成 RSA 或 DSA 密钥对,用于会话密钥的生成。
kex_algorithms 密钥交换算法,里边即包含我们使用的D-H算法,用于生成会话密钥
3. 认证阶段
- 客户端向服务器端发送认证请求,认证请求中包含用户名、认证方法、与该认证方法相关的内容(如:password认证时,内容为密码)。
- 服务器端对客户端进行认证,如果认证失败,则向客户端发送认证失败消息,其中包含可以再次认证的方法列表。
- 客户端从认证方法列表中选取一种认证方法再次进行认证。
- 该过程反复进行, 直到认证成功或者认证次数达到上限, 服务器关闭连接为止。
SSH提供两种认证方式:
password认证:客户端向服务器发出 password认证请求,将用户名和密码加密后发送给服务器;服务器将该信息解密后得到用户名和密码的明文,与设备上保存的用户名和密码进行比较,并返回认证成功或失败的消息。
publickey 认证:采用数字签名的方法来认证客户端。目前,设备上可以利用RSA和 DSA两种公共密钥算法实现数字签名。客户端发送包含用户名、公共密钥和公共密钥算法的 publickey 认证请求给服务器端。服务器对公钥进行合法性检查,如果不合法,则直接发送失败消息;否则,服务器利用数字签名对客户端进行认证,并返回认证成功或失败的消息
4.会话请求阶段
- 服务器等待客户端的请求;
- 认证通过后,客户端向服务器发送会话请求;
- 服务器处理客户端的请求。请求被成功处理后, 服务器会向客户端回应 SSH_SMSG_SUCCESS包,SSH进入交互会话阶段;否则回应 SSH_SMSG_FAILURE包,表示服务器处理请求失败或者不能识别请求。
5. 交互会话阶段
这也就是最后的阶段,客户端和服务端进行双向传输,服务端接收到数据后解密、执行、获取结果、加密、返回结果;客户端则解密、展示结果。
SSHv1 VS. SSHv2
到目前为止有两个不兼容的版本 SSH1 和 SSH2,而 SSH1 又分为 1.3 和 1.5 两个版本。
- 密钥交换算法。用于交换对称加密算法所使用的密钥,V1 采用 RSA 而 V2 采用了 Diffie-Hellman。
- 对称加密算法。用于加密在会话过程中传递的数据,例如 AES256-CTR 3DES-CBC BlowFish-CBC ArcFour 等。
SSH1采用DES、3DES、 Blowfish和RC4等对称加密算法保护数据安全传输,而对称加密算法的密钥是通过非对称加密算法(RSA)来完成交换的。SSH1使用循环冗余校验码(CRC)来保证数据的完整性,但是后来发现这种方法有缺陷。
虽然这个版本支持的功能比较多,但是感觉 libssh 的代码写的更加清晰一些。
参考
- The SSH Protocol 关于 SSH 中 RFC 协议的详细介绍,其中 Diffie-Hellman 相关的协商可以参考 RFC4419