SSH协议总结
一. 简介
SSH(Secure Shell) is a cryptographic<密码学> network protocol for operating network services securely over an unsecured network;
Typical(典型的) application include remote command-line, login, and remote command execution, but any network service can be secured with SSH.
1.1 版本信息
版本 | 时间 | 说明 |
---|---|---|
1.x | 1995年 | Tatu Ylönen写了SSH-1,前期版本使用很多开源的东西,后来版本去除了开源,变为收费软件; |
2.x | 2006年 | SSH-2 协议成为新的标准,相对于V1,V2支持DH,基于message authenticaiton codes的完整性校验,支持多个虚拟的shell session over a single SSH connection中; |
1.99 | RFC4253 | 非具体版本号,标识支持2.0及其以前版本SSH的服务器,表示向下兼容; |
1.2 实现
在1999年,开发者希望拥有一个免费的ssh软件,所以启用了最后的免费版本1.2.12,基于这个版本,Björn Grönvall开发了OSSH
,后来OpenBSD的开发者根据OSSH开发出了OpenSSH
,这是目前最流行的SSH实现,内嵌到很多的系统中;在OpenSSH 7.6
版本后,不再支持SSH-1,之前的版本是支持SSH的;
1.3 体系(architecture[ˈärkəˌtek(t)SHər])
The SSH-2 protocol has an internal architecture(defined in RFC 4251) with well-separated(分割) layers.
1.3.1 transport层
由RFC4253定义,其功能表明其工作在OSI参考模型的表示层,通常工作在TCP之上,主要功能:
- 处理最初的密钥交换及服务器验证;
- 负责对数据的encrytion [inˈkripSH(ə)n],compression [kəmˈpreSHən], integrity[inˈteɡrədē] verification[ˌverəfəˈkāSH(ə)n].
- 提供一个interface,上层通过该接口发送和接收明文包,每个包最大32768字节;
- 密钥重新交换,一般在传送了1GB数据或者1h后;
1.3.2 user authentication layer
由RFC4252定义,主要用于处理客户端的认证并提供一定种类的验证方法,常见的认证方式有:
- 使用password认证:使用username/pasword完成验证,无法完全避免“中间人”攻击;
- 基于publickey认证:基于密钥对的认证,支持DSA, DCDSA, RSA,X.509等;
- 交互时认证:即服务器发送一些提示,client根据提示输入仅本次有效的密码登陆;
- GSSAPI认证:为扩展的方案,使ssh可以使用Kerberos 5或NTLM之类的外部机制执行验证,尽管在Open-ssh中确实具有有效的GSSAPI的实现,但是这些方法通常由商业ssh实现并提供使用;
1.3.3 connection层
由RFC4254定义,这一次定义了channels的概念,一个connections中可以并行跑多个channel. Channel requests用来带外处理channel-specific的数据,比如变更终端窗口大小或者退出Server程序;总共有几种的channel:
- shell for termonial shell:为执行命令,或者为SFTP提供命令输入;
- direct-tcp/ip:client-to-server型的链接;
- forwarded-tcp/ip:server-to-client型链接;
tips: the connection layer provides the ability to multiplex many secondary sessions into a single SSH connection, a feature comparable to BEEP and not available in TLS.
1.3.4 SSHFP DNS record扩展
由RFC4255定义,提供一种将public host key fingerprints放置在DNS上,并通过使用DNSsec的方式传递,从而验证服务器真实性的方式;
二. 工作过程
ssh的工作过程共分为几步:
- TCP三次握手建立tcp链接;
- 版本协商阶段
- 算法协商阶段
- 密钥交换阶段
- 会话请求阶段
- 交互会话阶段
- TCP四次挥手断开tcp链接;
2.1 TCP的建立与断开
ssh在Internet中通常工作在TCP/IP层之上,基于tcp,端口使用22端口,所以在ssh开始前及结束后需要建立或者断开TCP链接;
三次握手:
四次挥手:
2.2 版本协商阶段
服务器及客户端在此步骤中交互各自的ssh版本及软件版本信息,过程如下:
在tcp建立链接之后,服务器向客户端发送“SSH-<主协议版本号>.<次协议版本号> - <软件版本号>”,客户端根据协议版本号,给出自己支持的版本号并发送给客户端,完成协商,否则断开tcp。
服务器向客户端发送自己的版本信息:
客户端回服务器自己的版本信息,完成协商:
2.3 算法协商
服务器端和客户端分别发送算法协商报文给对端,报文包含自己支持的公钥算法,加密算法,MAC算法,压缩算法等;
服务器&客户端发送的协商报文:
2.4 密钥交换阶段
ssh的密钥交换基于diffie-hellman-group-exchange-shaxxx<xxx如256>实现密钥的交换,过程如下:
client向server发送Exchange Request报文,开始密钥交换:
server向client发送Group报文,与client共享DH算法中的P和G:
client将使用p与g计算出的e发送给server;
server收到e后,根据算法计算出秘钥K,然后使用sha256算法将一些已知信息hash加密为H,并用rsa将hash签名,最后发送rsa的公钥,dh的f值,rsa签名后的hash信息发回客户端:
同时server也会在相同的报文中发送验证无误后发送的new-key报文:
客户端根据服务器发回的f计算出K值,并根据同样的已有信息hash计算得到H后,使用服务器发来的rsa-公钥校验服务器发回的hash值的签名,根据得到的hash值H,再进行特定的hash运算即可得到以后用于数据加密的密钥,校验无误返回new key,表示密钥交换完毕,以后数据均会被数据加密密钥加密;
2.4.1 H的计算
H = hash(V_C|V_S|I_C|I_S|K_S||e||f||K);
tips:可以看H中有只有client和server才知道的值K,也包含rsa公钥,所以hash后,中间人无法更改里面的值,如果更改了rsa的公钥与H的签名,其实也是没有意义的,因为在H中包含一个不易破解的rsa公钥;所以唯一有问题的点在于,你开始就不是在和你的目的主机在协商,而是中间人;
类型 | 值 | 说明 |
---|---|---|
string | V_C | 客户端的初始报文(版本信息:SSH-2.0-xxx,不含结尾的CR和LF) |
string | V_S | 服务器的初始报文 |
string | I_C | 客户端 SSH_MSG_KEX_INIT的有效载荷(不含开头的数据长度值) |
string | I_S | 服务器的同上 |
string | K_S | 主机秘钥(dh gex reply(33)过程服务器发送host key (RSA公钥)) |
mpint | e | 客户端DH公钥 |
mpint | f | 服务器DH公钥 |
mpint | K | 共同DH计算结果 |
将以上内容顺序拼接后,使用sha256计算出H,会话第一次的密钥交换生成的H为session_id,之后再进行密钥交换的H不再是session_id,session_id不变;
2.4.2 加密密钥的计算
加密密钥是一个对称加密算法(一般为aes算法)所用到的密钥,用于实际的数据加密。
计算:hash(K,H,单个字符,session_id);
单个字符指的是单个大写的ASCII字母,根据不同的加密秘钥选择不同的字符来计算。
字母 | 秘钥 |
---|---|
'A' | 客户端到服务器的初始IV(CBC) |
'B' | 服务器到客户端的初始IV |
'C' | 客户端到服务器的加密秘钥(数据加解密秘钥) |
'D' | 服务器到客户端的加密秘钥 |
'E' | 客户端到服务器的完整性秘钥(HMAC) |
'F' | 服务器到客户端的完整性秘钥 |
哈希计算得到字符串RE,如果我么想要的秘钥长度比RE长,则在RE后面继续加上一个hash值:hash(K,H,RE)成为一个加长的RE。还不够继续加上hash(K,H,RE),依次类推
2.4.3 DH算法
dh算法最终目的是在不安全的网络上完成K值的传递:
- server生成两个数
G
与P
,P
为非常大的素数,为DH算法的mod;G
为密码发生器;,将P
与G
发送给client; - 客户端生成一个
x
(0<x<P),计算e
=(G^x)%P,e
就是客户端的公钥,客户端将e
发送给服务器; - 服务器也同客户端一样,生成一个数
y
,计算f
=(G^y)%P。将服务器公钥f
发送给客户端; - 服务端与客户端分别计算
K1
与K2
,可以证明K1
=K2
,所以K
= K1=(e^y)%P = K2= (f^x)%P,完成K
的传递;
如上,中间人可以看到G,P,e,f,只要P足够大,中间人就无法根据这几个值计算出K;
如下为一段破解的程序,在x,y及G与P不大的情况下,根据e和f猜测x和y并得出K:
import random
# 生成P和G
g = random.randint(100010001000, 10000100010001000)
p = random.randint(100010001000, 10001000100010001000)
print("底数为%d, 模数为%d" % (g, p))
# 指数运算
def power(x):
return g ** x
# mod运算
def mod(x):
return x % p
# 根据最后生成的公钥e和f的值与P和G的值猜测x和y
def demod(e_or_f):
for x_or_y in range(10000):
if mod(power(x_or_y)) == e_or_f:
break
return x_or_y
x = random.randint(1000, 10000)
y = random.randint(1000, 10000)
print("client, server各自的源数字为:", [x, y])
e = mod(power(x)) # client加密后的公钥e,被我窃听
f = mod(power(y)) # server加密后的公钥f,被我窃听
K1 = mod(f ** x) # client计算出的K值
K2 = mod(e ** y) # server计算出的K值
guess_x = demod(e) # 猜测下x
guess_K1 = mod(f ** guess_x) # 根据猜测的x算出K1值;
guess_y = demod(f) # 猜测下y
guess_K2 = mod(e ** guess_y) # 根据y值算出K2值;
print("client,server各自算出的密钥为:", [K1, K2])
print("我猜测的client,server各自的源数字为:", [guess_x, guess_y])
print("我猜测的client,server各自算出的钥匙为:", [guess_K1, guess_K2])
2.5 用户认证阶段
tips:完成密钥交换节点后,所有数据均使用加密密钥加密的密文包;
目前用户阶段主要使用两种方式,password认证及publickey认证:
password认证方式:
- 客户端向服务器发出password认证请求,同时将用户名密码发送给服务器;
- 服务器将用户名与密码与服务器上的进行对比,返回成功或失败;
publickey认证方式:
- 客户端生成RSA公钥和私钥;
- 客户端将自己的公钥存放到服务器;
- 客户端请求连接服务器,服务器将一个随机字符串发送给客户端;
- 客户端根据自己的私钥加密这个随机字符串之后再发送给服务器;
- 服务器接受到加密后的字符串之后用公钥解密,如果正确就让客户端登录,否则拒绝。
tips:ssh2中以上两种方式可以混合使用,比如两种均需要验证或只验证其中任意一种(and与or);
整体认证过程如下:
-
客户端向服务器端发送认证请求,认证请求中包含用户名、认证方法、与该认证方法相关的内容(如:password认证时,内容为密码)。
-
服务器端对客户端进行认证,如果认证失败,则向客户端发送认证失败消息,其中包含可以再次认证的方法列表。
-
客户端从认证方法列表中选取一种认证方法再次进行认证。
-
该过程反复进行, 直到认证成功或者认证次数达到上限, 服务器关闭连接为止。
2.5.1 配置linux服务器的公钥登陆
-
在client生成密钥对
支持多种非对称密钥生成算法,如dsa,ecdsa和rsa等;
# -P参数表示密码,用于确保私钥的安全,可以使用ssh-agent来自动设置password的输入,一方方面是登陆方便,另一方面防止私钥丢失造成安全的隐患; # -f指定存放密钥的文件,公钥/私钥存放同一目录,公钥为.pub # -b采用长度为1024字节的公钥/私钥对,最长4096; ssh-keygen -b 2048 -t rsa -P # 该命令将在/root/.ssh目录下面产生一对密钥id_rsa和id_rsa.pub
-
将client下面的id_rsa.pub复制到Server的/root/.ssh/authorized_keys
# 如果.ssh/authorized_keys不存在则需要手动创建 scp /root/.ssh/id_rsa.pub root@server_ip:/root/.ssh/authorized_keys # 复制到某个用户的home下,即对某个用户的验证,比如这里,登陆上来即是root用户 chmod 600 /root/.ssh/authorized_keys # 只有root或当前用户才可以访问这个目录,这个是必须的,否则这个目录ssh会认为无效;
-
可以关闭密码登陆:
vim /etc/ssh/sshd_config PasswordAuthentication:no systemctl restart sshd
2.6 会话与交换阶段
会话请求后即计入交互会话阶段:
- 服务器等待客户端的请求;
- 认证通过后,客户端向服务器发送会话请求;
- 服务器处理客户端的请求。请求被成功处理后, 服务器会向客户端回应 SSH_SMSG_SUCCESS包,SSH进入交互会话阶段;否则回应 SSH_SMSG_FAILURE包,表示服务器处理请求失败或者不能识别请求。
- 客户端将要执行的命令加密后传给服务器;
- 服务器接收到报文,解密后执行该命令,将执行的结果加密发还给客户端;
- 客户端将接收到的结果解密后显示到终端上.
参考: