SSH协议
这是一篇去年学习ssh协议时写的文,基本上是SSH标准协议的翻译,放上来仅供参考,里边的一些细节我也有些忘记了。
概述
SSH是Secure SHell的缩写,即安全外壳协议,是一种在不安全网络提供安全远程登陆以及其他安全网络服务的协议。SSH是建立在应用层基础上的安全协议,可以有效防止远程管理过程中的信息泄露问题。
功能
主要功能
提供网络服务程序功能,加密传输数据,可抵抗中间人攻击。
应用场景
- 替代Telnet进行远程通信
- 提供安全FTP服务
- 为POP、PPP协议提供安全信道
组成
SSH协议由三个子协议组成:传输层协议(SSH-TRANS)、用户认证协议(SSH-USERAUTH)、连接协议(SSH-CONNECT)
SSH-TRANS
功能
安全的低级传输协议。提供服务器认证,保密性和完整性,还提供压缩功能。
通常运行在TCP/IP连接上,也可用于其他可靠数据流上。
身份验证是居于主机的,不执行用户认证,更高层的用户认证协议可以基于此协议设计。
规范
端口
通过TCP/IP使用时,端口默认为22
协议版本交换
标识字符串:
SSH-protoversion-softwareversion SP comments CR LF
‘protoversion’是”2.0”(现多为SSH2版本),’comments’字符串可选,若包含’comments’字符串,SP字符(空格符,ASCII 32)务必将’softwareversion’和’comments’字符串分开。标识字符串必须由单个CR字符(回车符,ASCII 13)和单个LF字符(换行符,ASCII 10)终止。
字符串中不能出现其他的空字符,最大长度为255个字符(包含回车和换行),在回车和换行(CR LF)之前的标识字符串部分用于Diffie-Hellman密钥交换。
实例:
SSH-2.0-OpenSSH CR LF
兼容性
服务端为新版本,客户端为旧版本时,服务端可兼容旧版协议
服务端为旧版本,客户端为新版本时,服务端无法识别,须使用旧版本访问
数据包格式
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
packet_length 数据包的长度(以字节为单位),不包括’mac’或’packet_length’字段本身。
padding_length 随机填充字节的长度
payload 有效载荷:数据包的游泳内容。默认压缩为无,已协商压缩,此字段将被压缩
random padding 随机填充:任意长度的填充,使得总长度是密码块大小或8的倍数,取其较大者。必须至少由四个字节的填充,最大填充量是255个字节
mac 消息认证码,默认为无,若一些上,此字段包含MAC字节
压缩
可选压缩,已协商压缩,则使用协商算法压缩“有效载荷”。
压缩方法:zlib可选ZLIB(LZ77)压缩
加密
加密算法和密钥将在密钥交换期间进行协商。当加密生效时,每个数据包的数据包长度,填充长度,有效载荷和填充字段必须用给定算法加密。
数据完整性
数据完整性通过在每个数据包中包含从共享密钥,数据包序列号和数据包内容计算出的MAC来加以保护。
消息认证算法和密钥在密钥交换期间进行协商,根据协商算法,计算方式为:
mac = MAC(key,sequence_number || unencrypted_packet)
unencrypted_packet是没有’mac’的整个分组
sequence_number是一个隐含的包序列号,表示为unit32格式
密钥交换
采用Diffie-Hellman方法。密钥交换(kex)始于每边发送支持算法的名称列表
产生两个制:一个共享密钥K和一个交换散列H。加密和认证密钥来自于这两个值
流程
- 客户端和服务器端想对方发送SSH版本识别字符串
- 收到版本字符串后,双方通过质地的那个格式二进制包进行通信
- 在传输完版本字符串后,客户端和服务端开始进行密钥交换(key exchange,简称kex)。Kex用来让客户端和服务器生成本次通信的密钥和序列号。在kex的最后一步,服务器给客户端发送他的公钥,客户端查询本地的known_hosts查找志告公钥验证服务器身份。在kex之后,客户端进行SSH-AUTH,请求服务器验证自己的身份。
SSH-AUTH
功能
SSH认证协议提供了客户端认证功能,向服务器发起请求,验证客户端的合法性。
认证方法
- password 密码验证
- public key 无密码公钥验证
- hostbased 基于主机的身份验证
规范
身份验证请求
消息格式:
byte SSH_MSG_USERAUTH_REQUEST
string user name(UTF-8)
string service name(ASCII)
string method name(ASCII)
.... method specific fields
如果用户名和服务名称发生改变,则断开连接
定义的方法名(method name):
"publickey" REQUIRED
"password" OPTIONAL
"hostbased" OPTIONAL
"none" NOT RECOMMENDED
认证协议消息号码
一般的认证信息代码:
SSH_MSG_USERAUTH_REQUEST 50
SSH_MSG_USERAUTH_FAILURE 51
SSH_MSG_USERAUTH_SUCCESS 52
SSH_MSG_USERAUTH_BANNER 53
此外还有一些为方法特定的消息保留的消息编号(60到79)
认证方法
1.公钥认证方法:publickey
a. 客户端创建自己的密钥对,将公钥提交到需要访问的服务器上。
b. 客户端向服务器发起请求,请求用私钥签名进行认证。请求报文:
byte SSH_MSG_USERAUTH_REQUEST
string user name
string service name
string "publickey"
boolean TRUE
string public key algorithm name
string public key to be used for authentication
string signature
其中签名项的内容为:
string session identifier
byte SSH_MSG_USERAUTH_REQUEST
string user name
string service name
string "publickey"
boolean TRUE
string public key algorithm name
string public key to be used for authentication
c. 服务器收到请求后,在公钥目录中查找对应用户,如果存在,则产生一个随机字符串,用公钥加密后发送给用户。
d. 用户收到字符串后,用自己的私钥进行解密,将解密后的字符串发送给服务器。
e. 服务器接收到字符串后与之前生成的随机字符串进行对比,如果一致,则允许用户登陆。
2.密码验证方法:password
客户端向服务器发起请求,发送包含自己用户名和对应密码的数据包,格式为:
byte SSH_MSG_USERAUTH_REQUEST
string user name
string service name
string "password"
boolean FALSE
string plaintext password(UTF-8)
数据包由传输层进行加密
3.基于主机的身份验证:hostbased
通过来源主机和用户名进行身份验证,客户端发送消息请求身份认证,格式为:
byte SSH_MSG_USERAUTH_REQUEST
string user name
string service name
string "hostbased"
string public key algorithm for host key
string public host key and certificates for client host
string client host name expressed as the FQDN(ASCII)
string user name on the client host(UTF-8)
string signature
签名的值为:
string session identifier
byte SSH_MSG_USERAUTH_REQUEST
string user name
string service name
string "hostbased"
string public key algorithm for host key
string public host key and certificates for client host
string client host name expressed as the FQDN(ASCII)
string user name on the client host(UTF-8)
SSH-CONN
功能
提供交互式登陆会话,远程执行命令,转发TCP/IP连接和转发X11连接。
channel机制
所有终端会话,转发连接等都是通过建立channel来通信的。任何一方都可以打开一个信道。多个信道被复用成一个单一的连接。
信道由末尾的数字进行标识。
信道是流量控制的。只有标为可用的窗口空间才可以发送数据。
打开信道
任何一端都可以开启一个新的信道,并分配一个本地信道号。发送请求,包含本地信道号和初始窗口大小。请求格式:
byte SSH_MSG_CHANNEL_OPEN
string channel type(ASCII)
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
.... channel type specific data follows
远端回复是否可以打开信道:
打开信道 SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION
uint32 recipient channel
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
.... channel type specific data follows
打开失败 SSH_MSG_CHANNEL_OPEN_FAILURE:
byte SSH_MSG_CHANNEL_OPEN_FAILURE
uint32 recipient channel
uint32 reason code
string description(UTF-8)
string language tag
原因代码(reason code)显示打开失败的原因
数据传输
在打开新的信道时窗口大小已经规定,可以通过以下消息进行调整:
byte SSH_MSG_CHANNEL_WINDOW_ADJUST
uint32 recipient channel
uint32 bytes to add
窗口大小指定对方可以发送多少个字节。
数据传输的消息格式:
byte SSH_MSG_CHANNEL_DATA
uint32 recipient channel
string data
关闭信道
当一方不再发送更多数据到一个信道时,应该发送SSH_MSG_CHANNEL_EOF,格式为:
byte SSH_MSG_CHANNEL_EOF
uint32 recipient channel
当任何一方希望终止信道时,发送SSH_MSG_CHANNEL_CLOSE。收到此消息后,另一方必须回复一个SSH_MSG_CHANNEL_CLOSE。则该信道被视为已关闭。
特定信道请求
许多信道类型值具有特定信道类型的扩展名。特定信道的请求格式为:
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string request type(ASCII)
boolean want reply
.... type-specific data follows
交互会话
会话是程序的远程执行,该程序可以是shell,应用程序,系统命令或者某些内置子系统。会涉及到虚拟终端,X11转发。可以同时激活多个会话。
开始会话
通过发送以下消息开始会话:
byte SSH_MSG_CHANNEL_OPEN
string "session"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
请求一个伪终端
通过发送以下消息请求一个伪终端:
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "pty-req"
boolean want_reply
string TERM environment variable value
uint32 terminal width, characters
uint32 terminal height, rows
uint32 terminal width, pixels
uint32 terminal height, pixels
string encoded terminal modes
X11转发
请求X11转发
通过发送SSH_MSG_CHANNEL_REQUEST消息请求X11转发:
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "x11-req"
boolean want reply
boolean single connection
string x11 authentication protocol
string x11 authentication cookie
uint32 x11 screen number
X11信道
X11信道以信道开启请求的形式开启,请求通过的结果是开启一个独立于会话的信道,关闭会话信道不会关闭X11转发信道。请求格式为:
byte SSH_MSG_CHANNEL_OPEN
string "x11"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
string originator address
uint32 originator port
接受到请求的一方应使用SSH_MSG_CHANNEL_OPEN_CONFIRMATION开启信道或者SSH_MSG_CHANNEL_OPEN_FAILURE开启信道失败进行响应。
环境变量传递
环境变量可以传递给shell/command。请求格式为:
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "env"
boolean want reply
string variable name
string variable value
启动shell/command
会话建立时,远程终端的一个程序开启。这个程序可以是shell,应用程序,或者独立于主机的子系统。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "shell"
boolean want reply
该消息请求开启一个用户默认的shell
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "exec"
boolean want reply
string command
该消息请求服务器执行给定的命令。’command’字符串可能包含一个路径。
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "subsystem"
boolean want reply
string subsystem name
该消息请求开启一个预定义的子系统。
会话数据传输
会话的数据传输使用SSH_MSG_CHANNEL\DATA和SSH_MSG_CHANNEL_EXTENDED_DATA包和窗口机制。SSH_MSG_CHANNEL_EXTENDED_DATA已被定义为stderr数据。
其他控制消息
- 窗口尺寸更改消息
- 本地流量控制
- 信号
返回退出状态
在另一端的命令终止时,会返回命令的退出状态。收到消息后,通过SSH_MSG_CHANNEL_CLOSE关闭信道。消息格式如下:
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "exit-signal"
boolean FALSE
string signal name (without the "SIG" prefix)
boolean core dumped
string error message(UTF-8)
string language tag
TCP/IP端口转发
请求端口转发
请求消息格式:
byte SSH_MSG_GLOBAL_REQUEST
string "tcpip-forward"
boolean want reply
string address to bind
uint32 port number to bind
TCP/IP转发信道
当连接达到远程转发的端口被请求时,打开一个信道将端口转发给另一个端口侧。请求消息格式:
byte SSH_MSG_CHANNEL_OPEN
string "forwarded-tcpip"
uint32 sender channel
uint32 initial window size
uint32 maximum packet size
string address that was connected
uint32 port that was connected
string originator IP address
uint32 originator port
工作过程
- 版本号协商阶段,通信双方确定使用的协议版本
- 密钥和算法协商阶段,通信双方协商出最终使用的算法
- 认证阶段,SSH客户端向服务器端发起认证请求,服务器端对客户端进行认证
- 会话请求阶段,认证通过后,客户端向服务器端发送会话请求
- 交互会话阶段,会话请求通过后,服务器端和客户端进行交互会话