You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不依赖外部库连接并认证SSH服务器?附代码框架求指导

可行性分析与实现方案

你的TCP框架骨架是可行的,它的读写线程分离、并发队列处理命令的结构符合网络通信的最佳实践。但要实现SSH功能,必须补全SSH应用层协议的核心逻辑——SSH不是简单的TCP字符串传输,它包含版本协商、密钥交换、加密通信、用户认证、会话管理等复杂流程,以下是具体的实现步骤:


1. 补全Connect方法的SSH核心流程

TCP连接建立后,必须完成SSH的版本协商、密钥交换、用户认证三步,才能进入可执行命令的状态:

private void Connect()
{
    try
    {
        TCP_Client = new TcpClient(computer.IpAddress, computer.IpPort)
        {
            NoDelay = true
        };
        TCP_Stream = TCP_Client.GetStream();
        TCP_Stream.ReadTimeout = Timeout.Infinite;

        // ====== 1. SSH版本协商 ======
        byte[] versionBuffer = new byte[1024];
        int readLen = TCP_Stream.Read(versionBuffer, 0, versionBuffer.Length);
        string serverVersion = Encoding.ASCII.GetString(versionBuffer, 0, readLen).Trim();
        if (!serverVersion.StartsWith("SSH-2.0-"))
            throw new Exception("目标不是SSH 2.0服务器");
        
        string clientVersion = "SSH-2.0-CustomSSHClient\r\n";
        byte[] clientVersionBytes = Encoding.ASCII.GetBytes(clientVersion);
        TCP_Stream.Write(clientVersionBytes, 0, clientVersionBytes.Length);
        TCP_Stream.Flush();

        // ====== 2. SSH密钥交换(KEX) ======
        // 此处需实现KEX_INIT消息交换、算法协商、共享密钥生成
        // 示例:协商使用diffie-hellman-group1-sha1算法,生成会话加密密钥
        PerformKeyExchange();
        if (!Connected)
            throw new Exception("密钥交换失败");

        // ====== 3. 用户认证(示例:密码认证) ======
        if (!AuthenticateWithPassword(computer.Username, computer.Password))
            throw new Exception("认证失败");

        pendingCommands = new ConcurrentQueue<string>();
        Connected = true;
        Authenticated = true;
    }
    catch (Exception e)
    {
        Disconnect();
    }
}

2. 实现SSH加密通信的基础方法

SSH在密钥交换后,所有数据都需要加密传输,需封装以下核心方法:

加密发送SSH数据包

private void SendEncryptedPacket(byte[] payload)
{
    // SSH包结构:长度(4字节大端) + 填充长度(1字节) + 有效载荷 + 填充
    int blockSize = _sessionCipher.BlockSize / 8; // 如AES-256的块大小为32字节
    int paddingLen = blockSize - ((payload.Length + 1) % blockSize);
    if (paddingLen < 4) paddingLen += blockSize; // SSH要求填充至少4字节

    List<byte> rawPacket = new List<byte>();
    // 添加长度(不含自身4字节)
    rawPacket.AddRange(BitConverter.GetBytes((uint)(payload.Length + 1 + paddingLen)).Reverse());
    // 添加填充长度
    rawPacket.Add((byte)paddingLen);
    // 添加有效载荷
    rawPacket.AddRange(payload);
    // 添加随机填充字节
    byte[] padding = new byte[paddingLen];
    new Random().NextBytes(padding);
    rawPacket.AddRange(padding);

    // 加密后发送
    byte[] encrypted = _sessionCipher.CreateEncryptor().TransformFinalBlock(rawPacket.ToArray(), 0, rawPacket.Count);
    TCP_Stream.Write(encrypted, 0, encrypted.Length);
    TCP_Stream.Flush();
}

解密读取SSH数据包

private byte[] ReadEncryptedPacket()
{
    // 读取加密的长度字段并解密
    byte[] encryptedLen = new byte[4];
    TCP_Stream.Read(encryptedLen, 0, 4);
    byte[] lenBytes = _sessionCipher.CreateDecryptor().TransformFinalBlock(encryptedLen, 0, 4);
    uint totalLen = BitConverter.ToUInt32(lenBytes.Reverse().ToArray(), 0);

    // 读取剩余加密数据并解密
    byte[] encryptedRest = new byte[totalLen];
    int read = 0;
    while (read < totalLen)
    {
        int chunk = TCP_Stream.Read(encryptedRest, read, (int)totalLen - read);
        if (chunk == 0) throw new IOException("连接已关闭");
        read += chunk;
    }
    byte[] decryptedRest = _sessionCipher.CreateDecryptor().TransformFinalBlock(encryptedRest, 0, encryptedRest.Length);

    // 提取有效载荷(跳过填充长度和填充部分)
    byte paddingLen = decryptedRest[0];
    return decryptedRest.Skip(1).Take((int)(totalLen - 1 - paddingLen)).ToArray();
}

3. 补全TCP_Write的命令发送逻辑

SSH执行命令需要先建立会话通道,再发送exec请求:

private void TCP_Write()
{
    uint? currentChannelId = null;
    while (isRunning)
    {
        try
        {
            if (!Connected)
            {
                Connect();
            }
            else
            {
                while (pendingCommands.TryDequeue(out string commandString))
                { 
                    // 1. 打开SSH会话通道
                    if (!currentChannelId.HasValue)
                    {
                        currentChannelId = OpenSessionChannel();
                    }

                    // 2. 发送exec命令请求
                    List<byte> execRequest = new List<byte>();
                    execRequest.Add((byte)98); // SSH_MSG_CHANNEL_REQUEST类型
                    // 添加通道ID
                    execRequest.AddRange(BitConverter.GetBytes((uint)currentChannelId).Reverse());
                    // 添加请求类型:exec
                    byte[] execType = Encoding.UTF8.GetBytes("exec");
                    execRequest.AddRange(BitConverter.GetBytes((uint)execType.Length).Reverse());
                    execRequest.AddRange(execType);
                    // 要求服务器回复
                    execRequest.Add(0x01);
                    // 添加命令内容
                    byte[] commandBytes = Encoding.UTF8.GetBytes(commandString);
                    execRequest.AddRange(BitConverter.GetBytes((uint)commandBytes.Length).Reverse());
                    execRequest.AddRange(commandBytes);

                    SendEncryptedPacket(execRequest.ToArray());
                }
                TCP_FlushEvt.Reset();
                TCP_FlushEvt.Wait();
            }
        }
        catch (Exception e)
        {
            Disconnect();
        }
    }
}

4. 补全TCP_Read的响应解析逻辑

需要解析SSH返回的通道数据、错误信息等:

private void TCP_Read()
{
    byte[] buffer = new byte[4096];
    while (isRunning)
    {
        try
        {
            if (Connected && Authenticated)
            {
                byte[] payload = ReadEncryptedPacket();
                byte msgType = payload[0];
                switch (msgType)
                {
                    case 94: // SSH_MSG_CHANNEL_DATA(命令标准输出)
                        uint channelId = BitConverter.ToUInt32(payload.Skip(1).Take(4).Reverse().ToArray(), 0);
                        uint dataLen = BitConverter.ToUInt32(payload.Skip(5).Take(4).Reverse().ToArray(), 0);
                        byte[] outputData = payload.Skip(9).Take((int)dataLen).ToArray();
                        Debug.WriteLine($"命令输出:{Encoding.UTF8.GetString(outputData)}");
                        break;
                    case 95: // SSH_MSG_CHANNEL_EXTENDED_DATA(命令错误输出)
                        // 类似标准输出处理,提取错误内容
                        break;
                    case 96: // SSH_MSG_CHANNEL_EOF(命令执行结束)
                        Debug.WriteLine("命令执行完成");
                        break;
                    // 处理其他消息类型:心跳、错误等
                }
            }
        }
        catch
        {
            Disconnect();
        }
    }
    Disconnect();
}

5. 完善Disconnect的资源清理

private void Disconnect()
{
    isRunning = false;
    TCP_FlushEvt.Set(); // 唤醒阻塞的写线程
    TCP_Stream?.Close();
    TCP_Client?.Close();
    _sessionCipher?.Clear(); // 清理加密对象
    Connected = false;
    Authenticated = false;
}

关键注意事项

  1. SSH协议复杂度:手动实现需要处理大量细节,比如算法协商、MAC校验、重放保护等,上述示例仅覆盖核心流程,实际使用需完善所有协议细节。
  2. 线程安全:Connected、Authenticated等状态变量需用volatile修饰或加锁,避免多线程竞争问题。
  3. 错误处理:需针对SSH的各种错误消息(如认证失败、通道错误)添加分支处理,提升鲁棒性。

内容的提问来源于stack exchange,提问作者Hugo_vdms

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 21:51:48