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之上,主要功能:

  1. 处理最初的密钥交换及服务器验证;
  2. 负责对数据的encrytion [inˈkripSH(ə)n],compression [kəmˈpreSHən], integrity[inˈteɡrədē] verification[ˌverəfəˈkāSH(ə)n].
  3. 提供一个interface,上层通过该接口发送和接收明文包,每个包最大32768字节;
  4. 密钥重新交换,一般在传送了1GB数据或者1h后;

1.3.2 user authentication layer

由RFC4252定义,主要用于处理客户端的认证并提供一定种类的验证方法,常见的认证方式有:

  1. 使用password认证:使用username/pasword完成验证,无法完全避免“中间人”攻击;
  2. 基于publickey认证:基于密钥对的认证,支持DSA, DCDSA, RSA,X.509等;
  3. 交互时认证:即服务器发送一些提示,client根据提示输入仅本次有效的密码登陆;
  4. GSSAPI认证:为扩展的方案,使ssh可以使用Kerberos 5或NTLM之类的外部机制执行验证,尽管在Open-ssh中确实具有有效的GSSAPI的实现,但是这些方法通常由商业ssh实现并提供使用;

1.3.3 connection层

由RFC4254定义,这一次定义了channels的概念,一个connections中可以并行跑多个channel. Channel requests用来带外处理channel-specific的数据,比如变更终端窗口大小或者退出Server程序;总共有几种的channel:

  1. shell for termonial shell:为执行命令,或者为SFTP提供命令输入;
  2. direct-tcp/ip:client-to-server型的链接;
  3. 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的工作过程共分为几步:

  1. TCP三次握手建立tcp链接;
  2. 版本协商阶段
  3. 算法协商阶段
  4. 密钥交换阶段
  5. 会话请求阶段
  6. 交互会话阶段
  7. TCP四次挥手断开tcp链接;

2.1 TCP的建立与断开

ssh在Internet中通常工作在TCP/IP层之上,基于tcp,端口使用22端口,所以在ssh开始前及结束后需要建立或者断开TCP链接;

三次握手:

TCP三次握手

四次挥手:

TCP四次挥手

2.2 版本协商阶段

服务器及客户端在此步骤中交互各自的ssh版本及软件版本信息,过程如下:

在tcp建立链接之后,服务器向客户端发送“SSH-<主协议版本号>.<次协议版本号> - <软件版本号>”,客户端根据协议版本号,给出自己支持的版本号并发送给客户端,完成协商,否则断开tcp。

服务器向客户端发送自己的版本信息:

ssh-server版本发送

客户端回服务器自己的版本信息,完成协商:

ssh-client版本回复

2.3 算法协商

服务器端和客户端分别发送算法协商报文给对端,报文包含自己支持的公钥算法,加密算法,MAC算法,压缩算法等;

服务器&客户端发送的协商报文:

ssh-server&client密钥init

2.4 密钥交换阶段

ssh的密钥交换基于diffie-hellman-group-exchange-shaxxx<xxx如256>实现密钥的交换,过程如下:

client向server发送Exchange Request报文,开始密钥交换:

ssh-exchange requests

server向client发送Group报文,与client共享DH算法中的P和G:

ssh-group-p&g

client将使用p与g计算出的e发送给server;

ssh-init-e

server收到e后,根据算法计算出秘钥K,然后使用sha256算法将一些已知信息hash加密为H,并用rsa将hash签名,最后发送rsa的公钥,dh的f值,rsa签名后的hash信息发回客户端:

ssh-reply

同时server也会在相同的报文中发送验证无误后发送的new-key报文:

ssh-new-key-server

客户端根据服务器发回的f计算出K值,并根据同样的已有信息hash计算得到H后,使用服务器发来的rsa-公钥校验服务器发回的hash值的签名,根据得到的hash值H,再进行特定的hash运算即可得到以后用于数据加密的密钥,校验无误返回new key,表示密钥交换完毕,以后数据均会被数据加密密钥加密;

ssh-new-key-client

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值的传递:

  1. server生成两个数GP,P为非常大的素数,为DH算法的mod;G为密码发生器;,将PG发送给client;
  2. 客户端生成一个x(0<x<P),计算e=(G^x)%P,e就是客户端的公钥,客户端将e发送给服务器;
  3. 服务器也同客户端一样,生成一个数y,计算f=(G^y)%P。将服务器公钥f发送给客户端;
  4. 服务端与客户端分别计算K1K2,可以证明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])

dh破解图

2.5 用户认证阶段

tips:完成密钥交换节点后,所有数据均使用加密密钥加密的密文包;

目前用户阶段主要使用两种方式,password认证及publickey认证:

password认证方式:

  1. 客户端向服务器发出password认证请求,同时将用户名密码发送给服务器;
  2. 服务器将用户名与密码与服务器上的进行对比,返回成功或失败;

publickey认证方式:

  1. 客户端生成RSA公钥和私钥;
  2. 客户端将自己的公钥存放到服务器;
  3. 客户端请求连接服务器,服务器将一个随机字符串发送给客户端;
  4. 客户端根据自己的私钥加密这个随机字符串之后再发送给服务器;
  5. 服务器接受到加密后的字符串之后用公钥解密,如果正确就让客户端登录,否则拒绝。

tips:ssh2中以上两种方式可以混合使用,比如两种均需要验证或只验证其中任意一种(and与or);

整体认证过程如下:

  1. 客户端向服务器端发送认证请求,认证请求中包含用户名、认证方法、与该认证方法相关的内容(如:password认证时,内容为密码)。

  2. 服务器端对客户端进行认证,如果认证失败,则向客户端发送认证失败消息,其中包含可以再次认证的方法列表。

  3. 客户端从认证方法列表中选取一种认证方法再次进行认证。

  4. 该过程反复进行, 直到认证成功或者认证次数达到上限, 服务器关闭连接为止。

2.5.1 配置linux服务器的公钥登陆

  1. 在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
    
  2. 将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会认为无效;
    
  3. 可以关闭密码登陆:

    vim /etc/ssh/sshd_config
    PasswordAuthentication:no
    systemctl restart sshd
    

2.6 会话与交换阶段

会话请求后即计入交互会话阶段:

  1. 服务器等待客户端的请求;
  2. 认证通过后,客户端向服务器发送会话请求;
  3. 服务器处理客户端的请求。请求被成功处理后, 服务器会向客户端回应 SSH_SMSG_SUCCESS包,SSH进入交互会话阶段;否则回应 SSH_SMSG_FAILURE包,表示服务器处理请求失败或者不能识别请求。
  4. 客户端将要执行的命令加密后传给服务器;
  5. 服务器接收到报文,解密后执行该命令,将执行的结果加密发还给客户端;
  6. 客户端将接收到的结果解密后显示到终端上.

参考:

密钥交换算法详解:https://www.cnblogs.com/wchrt/p/4534463.html

posted @ 2021-03-08 16:20  FcBlogs  阅读(932)  评论(0编辑  收藏  举报