了解QSsh —— qt版的ssh实现 (四) ssh交互了解

打开了之前看到的两个CREATOR_SSH_DEBUG调试

先看下state状态

// NOTE: When you add stuff here, don‘t forget to update m_packetHandlers.
enum SshStateInternal {
    SocketUnconnected, // initial and after disconnect
    SocketConnecting, // After connectToHost()
    SocketConnected, // After socket‘s connected() signal
    UserAuthServiceRequested,
    UserAuthRequested,
    ConnectionEstablished // After service has been started
    // ...
};

2是SocketConnected,是前面3个包。

3是UserAuthServiceRequested,第四个包,说明第3个包处理之后就是认证服务请求了。

4是UserAuthRequested,第五个包。

5,服务已经启动了,说明五个包之后服务就起来了。

这个很重要,需要结合这个来分析抓到的包。差点忘了,这些是incoming接受到server的第几个包,差点惯性认为是交互的第几个包了。一开始写的时候就想错了,现在回过来说明下。这里的包状态说明使用(1)(2)(3)(4)(5)...来说明

登陆成功后显示信息

上面整个过程抓到的

第一个包,分析过了,连接成功后状态就是SocketConnected,然后发送ClientId

第二个包,状态为SocketConnected,第(1)个包,server返回ServerId

第三个包,状态为SocketConnected,之前就分析到第二个包,现在知道了收到ServerId后要发送Key Exchange Init,现在我依然没有去看SSH协议介绍,只说我的理解。这个应该是告诉Server,client这边key交换已经初始化好了

第四个包,状态为SocketConnected,第(2)个包,Server发送Key Exchange Init,同样应该是告诉client,server这边key交换已经初始化好了

第五个包,状态为SocketConnected,client发送Diffle-Hellman Key Exchange Init,具体是什么意思,后面看SSH协议时再去理解。这里分析完后,在本文文末还会拿点SSH协议内容来,来验证分析是否正确。

第六个包,状态为SocketConnected,第(3)个包,server发送了Diffle-Hellman Key Exchange reply,并有New Keys。既然是exchange,那么这里应该是server的new
keys信息吧,不是让client发送new keys的意思吧?看后面协议验证。

第七个包,状态可能为SocketConnected,也可能为UserAuthServiceRequested,待确定。client 发送自己的new keys。UserAuthServiceRequested之后还有个UserAuthRequested。

第七个包之后,都是加密数据包了。第(4)个包及之后。UserAuthRequested?

哎,那用户名和密码是在什么时候发送过去的?哦,我忽略了一个重要信息,就是字节数。

第八个包,len=52,第九个包,len=36。而第(4)个包len=52,第(5)个包len=36。那说明,这里才是用户名和密码的验证及后续信息传输的开始。也对,用户名和密码是登陆shell的,算shell信息,而不是ssh信息。

Diffie-Hellman:一种确保共享KEY安全穿越不安全网络的方法,它是OAKLEY的一个组成部分。Whitefield与Martin
Hellman在1976年提出了一个奇妙的密钥交换协议,称为Diffie-Hellman密钥交换协议/算法(Diffie-Hellman Key Exchange/Agreement Algorithm).这个机制的巧妙在于需要安全通信的双方可以用这个方法确定对称密钥。然后可以用这个密钥进行加密和解密。但是注意,这个密钥交换协议/算法只能用于密钥的交换,而不能进行消息的加密和解密。双方确定要用的密钥后,要使用其他对称密钥操作加密算法实际加密和解密消息。

http://blog.csdn.net/gsnumen/article/details/7293266《ssh详细登录过程》

认证原理:

有2种认证方式:

基于账号和口令的验证方式 和 基于公钥和私钥的验证方式

ssh的登录过程分为5个阶段

1、版本号协商阶段

2、密钥和算法协商阶段

3、认证阶段

4、会话请求阶段

5、会话交互阶段

1、版本号协商阶段

服务端打开端口22,等待客户连接。

客户端向服务端发起TCP连接,连接建立后,服务端向客户端发送第一个报文,包括版本标志字符串,格式为“协议版本号 次协议版本号 软件版本号”。

客户端收到报文后,解析协议版本号,如果服务端的协议版本号比自己的低,且客户端能支持服务端的低版本,就使用服务端的协议号,否则使用自己的协议版本号。

客户端回复服务端一个报文,包含了客户端决定使用的协议版本号。

服务端比较客户端发过来的版本号,决定是否能同客户端交互。

如果协商成功,就进入密钥和算法协商阶段。否则服务端断开TCP连接。

2、密钥和算法协商阶段

服务端和客户端分别发送算法协商报文给对方,报文中包含自己支持的公钥算法列表、加密算法列表、消息验证码算法列表、压缩算法列表等。

服务端和客户端根据对方和自己支持的算法得出最终使用的算法。

服务端和客户端利用DH交换算法、主机密钥对等参数,生成会话密钥和会话ID。

c公 客户端公钥

c密 客户端密钥

s公 服务端公钥

s密 服务端密钥

在版本号协商阶段完成后:

服务端将 s公 发送给客户端。

服务端生成会话ID ,设为 id ,发送给客户端。

客户端生成会话密钥,设为 key ,并计算 res = id 异或 key。

客户端将 res 用 s公 进行加密,将结果发送给服务端。

服务端用 s密 进行解密,得到 res。

服务器计算 res 异或 id,得到 key。

至此服务端和客户端都知道了会话密钥和会话ID,以后的数据传输都使用会话密钥进行加密和解密。

3、认证阶段

基于账号和口令的验证方式:

客户端使用密钥和算法协商阶段生成的会话密钥加密账号、认证方法、口令,将结果发送给服务器。

服务端使用获得的会话密钥解密报文,得到账号和口令。

服务端对这个账号和口令进行判断,如果失败,向客户端发送认证失败报文,其中包含了可以再次认证的方法列表。

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

这个过程反复进行,直到认证成功或者认证次数达到上限,服务端关闭本次TCP连接。

基于公钥和私钥的验证方式:

使用ssh-keygen程序生成公钥 id_dsa.pub 和私钥 id_dsa,一般是在客户端上生成,然后把 id_dsa.pub 通过某种方式发送给服务端。

服务端放在将要远程登录过来的那个账号的目录的.ssh目录下面。

客户端使用密钥和算法协商阶段生成的会话密钥加密账号、认证方法、id_dsa.pub,将结果发送给服务端。

服务端使用会话密钥解密报文,得到账号、id_dsa.pub。    服务端在这个账号的目录的.ssh目录下找对应的公钥,如果没有找到,发送失败消息给客户端,如果找到,比较客户发送过来的这个公钥和找到的公钥,如果内容相同,服务端生成一个随机的字符串,简称“质询”,然后使用找到的公钥加密这个质询,然后使用会话密钥再次加密。

服务端把这个双重加密的数据发送给客户端。

客户端使用会话密钥解密报文,然后使用id_dsa再次解密数据,得到质询。

客户端使用会话密钥加密质询,发送给服务端。

服务端使用会话密钥解密报文,得到质询,判断是不是自己生成的那个质询,如果不相同,发送失败消息给客户端,如果相同,认证通过。

我配置sshd时使用密码登录,没有开启密钥认证。

客户端回复服务端一个报文,包含了客户端决定使用的协议版本号。

服务端比较客户端发过来的版本号,决定是否能同客户端交互。

如果协商成功,就进入密钥和算法协商阶段。否则服务端断开TCP连接。

这个似乎没有,第三个包是key exchange,可以理解为密钥协商。客户端没有再回复一个决定使用版本号的包啊?我是觉得没有回复。

就到这里吧,再去看下代码。

看了一下,发现之前看的

// RFC 4253, 4.2.
void SshConnectionPrivate::handleServerId()
{
#ifndef CREATOR_SSH_DEBUG
//#ifdef CREATOR_SSH_DEBUG
    qDebug("%s: incoming data size = %d, incoming data = ‘%s‘",
        Q_FUNC_INFO, m_incomingData.count(), m_incomingData.data());
#endif
    const int newLinePos = m_incomingData.indexOf(‘\n‘);
    if (newLinePos == -1)
        return; // Not enough data yet.

    // Lines not starting with "SSH-" are ignored.
    if (!m_incomingData.startsWith("SSH-")) {
        m_incomingData.remove(0, newLinePos + 1);
        m_serverHasSentDataBeforeId = true;
        return;
    }

    if (newLinePos > 255 - 1) {
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Identification string too long.",
            tr("Server identification string is %n characters long, but the maximum "
               "allowed length is 255.", 0, newLinePos + 1));
    }

    const bool hasCarriageReturn = m_incomingData.at(newLinePos - 1) == ‘\r‘;
    m_serverId = m_incomingData.left(newLinePos);
    if (hasCarriageReturn)
        m_serverId.chop(1);
    m_incomingData.remove(0, newLinePos + 1);

    if (m_serverId.contains(‘\0‘)) {
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Identification string contains illegal NUL character.",
            tr("Server identification string contains illegal NUL character."));
    }

    // "printable US-ASCII characters, with the exception of whitespace characters
    // and the minus sign"
    QString legalString = QLatin1String("[]!\"#$!&‘()*+,./0-9:;<=>[email protected][\\\\^_`a-z{|}~]+");
    const QRegExp versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?").arg(legalString));
    if (!versionIdpattern.exactMatch(QString::fromLatin1(m_serverId))) {
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
            "Identification string is invalid.",
            tr("Server Identification string \"%1\" is invalid.")
                    .arg(QString::fromLatin1(m_serverId)));
    }
    const QString serverProtoVersion = versionIdpattern.cap(1);
    if (serverProtoVersion != QLatin1String("2.0") && serverProtoVersion != QLatin1String("1.99")) {
        throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
            "Invalid protocol version.",
            tr("Server protocol version is \"%1\", but needs to be 2.0 or 1.99.")
                    .arg(serverProtoVersion));
    }

    if (m_connParams.options & SshEnableStrictConformanceChecks) {
        if (serverProtoVersion == QLatin1String("2.0") && !hasCarriageReturn) {
            throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
                    "Identification string is invalid.",
                    tr("Server identification string is invalid (missing carriage return)."));
        }

        if (serverProtoVersion == QLatin1String("1.99") && m_serverHasSentDataBeforeId) {
            throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
                    "No extra data preceding identification string allowed for 1.99.",
                    tr("Server reports protocol version 1.99, but sends data "
                       "before the identification string, which is not allowed."));
        }
    }

    m_keyExchange.reset(new SshKeyExchange(m_sendFacility));
    m_keyExchange->sendKexInitPacket(m_serverId);
    m_keyExchangeState = KexInitSent;
}

提示中就说了need ssh2.0或者ssh1.99,所以对于QSsh确实没有回复的。这些都处理好后,就直接sendKeyInitPacket()了,也就是Key Exchange Init。

时间: 2024-10-10 12:53:53

了解QSsh —— qt版的ssh实现 (四) ssh交互了解的相关文章

Qt版音乐播放器

    Qt版音乐播放器 转载请标明出处:牟尼的专栏 http://blog.csdn.net/u012027907 一.关于Qt 1.1 什么是Qt Qt是一个跨平台应用程序和UI开发框架.使用Qt只需一次性开发应用程序,无需重新编写源代码,便可跨不同桌面和嵌入式操作系统部署这些应用程序. Qt Creator是全新的跨平台Qt IDE,可单独使用,也可与Qt库和开发工具组成一套完整的SDK,其中包括:高级C++代码编辑器,项目和集成管理工具,集成的上下文相关的帮助系统,图形化调试器,代码管理

Qt版贪吃蛇游戏

Qt版贪吃蛇游戏 转载请标明出处:牟尼的专栏 http://blog.csdn.net/u012027907 最近在学习Qt,用了一个多月的时间掌握了Qt中最基本的知识,也完成了<Qt版音乐播放器>.<Qt版贪吃蛇游戏>.<Qt版双人俄罗斯方块>以及<Qt版科学计算器>等,之前在VC下写过这些程序,所以在Qt下只是改变了显示等语句,我写过<C++版贪吃蛇游戏>.<VC版贪吃蛇游戏>,当时将与显示等无关的东西封装起来,在Qt下直接用,只

Qt版科学计算器

Qt版科学计算器 转载请标明出处:牟尼的专栏 http://blog.csdn.net/u012027907 之前做过<VC版科学计算器>,这也是我学VC++时的第一个大作业,通过科学计算器的开发使用我学到了很多东西,也让我逐渐喜欢上了编程.最近在学习Qt,所以将当时在VC下写过的一些东西在Qt下重写了一遍,其实主要还是与显示等有关的东西需要重写,要使用Qt的显示方式,而其他的核心的算法等都还是使用VC下C++的源码. 下面是Qt版的运行截图: 标准版: 科学版: 头文件中变量和槽的声明: c

Qt版双人俄罗斯方块游戏

Qt版双人俄罗斯方块游戏 转载请标明出处:牟尼的专栏 http://blog.csdn.net/u012027907 之前写过<VC版双人俄罗斯方块>,将其在Qt下又重写了一遍,核心的算法还是采用VC下曾经用过的C++源码,直接用的是当时封装好的类,写在一个文件中,所以直接拿来用.不同的仍然是与显示有关的东西需要采用Qt下的显示方式. 以下是Qt版运行截图: 先是按键响应槽的实现代码: void Tetris::keyPressEvent(QKeyEvent *event) { if(even

QT开发(五十四)———QML组件

QT开发(五十四)---QML组件 QML组件是由基本元素组合成的一个复杂的可重用的组合元素.QML 提供了多种方法来创建组件. 基于文件的组件将QML元素放置在一个单独的文件中,然后给文件一个名字,可以通过名字来使用组件.如果有一个文件名为Cell.qml,就可以在QML中使用Cell { - }形式.自定义组件的文件名的首字母必须大写. Cell.qml文件: import QtQuick 2.0   Item {     id: container     property alias c

【C/C++学院】(19)QT版:记事本

利用主窗口类实现,QT版:记事本. #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QCloseEvent> #include <QTextEdit> #include <QMenu> #include <QMenuBar> #include <QAction> class MainWindow : public QMainWindo

QT开发(二十四)——QT文件操作

QT开发(二十四)--QT文件操作 一.QT文件操作简介 QT中的IO操作通过统一的接口简化了文件与外部设备的操作方式,QT中文件被当作一种特殊的外部设备,文件操作与外部设备操作相同. 1.IO操作的主要函数接口 打开设备:bool open(OpenMode mode) 读取数据:QByteArray read(qint64 maxSize) 写入数据:qint64 write(const QByteArray & byteArray) 关闭设备:void close() IO操作的本质是连续

Qt移动应用开发(四):应用粒子特效

上一篇文章介绍了Qt Quick是怎样对帧动画进行支持的.帧动画的实现离不开状态机.而状态机.动画和状态切换(transitions)则是Qt框架的核心内容,也就是说它们可以建立在任何一个QObject对象中而不必非得依赖Qt的任何图形显示模块.拿一个例子说吧,如果你想实现背景音乐的平滑过渡,你可以不用写多余的代码,将背景音乐的音量作一下动画插值就可以达到效果了.事实上我制作的游戏<吃药了>就是这么实现效果的.而这一篇文章将要聚焦的是Qt Quick另外一个非常强大的系统--粒子系统. 原创文

QT开发(三十四)——QT多线程编程

QT开发(三十四)--QT多线程编程 一.QT多线程简介 QT通过三种形式提供了对线程的支持,分别是平台无关的线程类.线程安全的事件投递.跨线程的信号-槽连接. QT中线程类包含如下: QThread 提供了开始一个新线程的方法    QThreadStorage 提供逐线程数据存储    QMutex 提供相互排斥的锁,或互斥量    QMutexLocker 是一个辅助类,自动对 QMutex 加锁与解锁    QReadWriterLock 提供了一个可以同时读操作的锁    QReadL