如何修复 OpenSSL 不正确的数据传输?

How to fix OpenSSL incorrect data transferring?

我需要使用 OpenSSL 传输序列化数据。我的代码有效,但在某些情况下,传输的数据有一些差异。

这是序列化数据http://s000.tinyupload.com/?file_id=02935614701824936895

int sslWrite(SSL *ssl, const void *buf, int num)
{
    const int MAX_SIZE = 0x10000;

    const int INT_SIZE = sizeof(int);
    const int numv = INT_SIZE + num;

    if (numv > MAX_SIZE) {
        return -1;
    }

    std::vector<char> bufv(numv);

    memcpy(&bufv[0], &numv, INT_SIZE);
    if (num != 0) {
        memcpy(&bufv[INT_SIZE], buf, num);
    }

    int n = 0;
    while (n < num) {
        int w = SSL_write(ssl, &bufv[0], numv - n);
        if (w < 0) {
            return 0;
        }
        n += w;
    }

    return n;
}

int sslRead(SSL *ssl, void *buf, int num)
{
    const int INT_SIZE = sizeof(int);
    const int BUF_SIZE = 0x10000;
    char bufv[BUF_SIZE];

    int m = SSL_read(ssl, (char *)bufv, num);

    if (m < 0) {
        return m;
    }

    const int n = *(int *)(bufv);

    if (n > BUF_SIZE - INT_SIZE) {
        return -1;
    }

    while (m < n) {
        int k = SSL_read(ssl, (char *)bufv + m, num - m);
        if (k < 0) {
            return k;
        }
        m += k;
    }

    memcpy(buf, &bufv[INT_SIZE], n - INT_SIZE);
    return n - INT_SIZE;
}

-

// Server
#include <fstream>
#include <WS2tcpip.h>
#include <Winsock2.h>
#define _WINSOCKAPI_
#include <windows.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/applink.c>

int OpenListener(int port)
{
    int sd;
    struct sockaddr_in addr;
    sd = socket(PF_INET, SOCK_STREAM, 0);
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
        perror("can't bind port");
        abort();
    }
    if (listen(sd, 10) != 0) {
        perror("Can't configure listening port");
        abort();
    }
    return sd;
}

SSL_CTX* InitServerCTX(void)
{
    const SSL_METHOD *method;
    SSL_CTX *ctx;
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    method = SSLv2_server_method();
    ctx = SSL_CTX_new(method);
    if (ctx == NULL) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    return ctx;
}

void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile)
{
    if (SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    if (!SSL_CTX_check_private_key(ctx)) {
        fprintf(stderr, "Private key does not match the public certificate\n");
        abort();
    }
}

int main()
{
    SSL_CTX *ctx;
    int server;

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    SSL_library_init();

    ctx = InitServerCTX();
    LoadCertificates(ctx, "sert.crt", "sert.key");
    server = OpenListener(3456);

    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);

    int client = accept(server, (struct sockaddr*)&addr, &len);

    SSL *ssl;
    printf("Connection: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, client);

    if (SSL_accept(ssl) == -1) {
        ERR_print_errors_fp(stderr);
    }
    else {
        std::ifstream fin("data", std::ios::binary);
         std::string data((std::istreambuf_iterator<char>(fin)), std::istreambuf_iterator<char>());
        fin.close();

        sslWrite(ssl, &data[0], (int)data.size());

        const int BUF_SIZE = 0x10000;
        std::string data1;
        data1.resize(BUF_SIZE);
        int n = sslRead(ssl, &data1[0], BUF_SIZE);
        data1.resize(n);

        /////////////////////////////////////////
        bool b = (data == data1); // b - FALSE!!!
        /////////////////////////////////////////
    }

    SSL_free(ssl);
    closesocket(client);
}

-

// Client
#include <string>
#include <WS2tcpip.h>
#include <Winsock2.h>
#define _WINSOCKAPI_
#include <windows.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/applink.c>

SSL_CTX *InitCTX(void)
{
    const SSL_METHOD *method;
    SSL_CTX *ctx;
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    method = SSLv2_client_method();
    ctx = SSL_CTX_new(method);
    if (ctx == NULL) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    return ctx;
}

int OpenConnection(const char *hostName, int port)
{
    struct hostent *host;
    struct sockaddr_in addr;
    if ((host = gethostbyname(hostName)) == NULL) {
        perror(hostName);
        abort();
    }
    int sd = socket(PF_INET, SOCK_STREAM, 0);
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = *(long*)(host->h_addr);
    if (connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
        closesocket(sd);
        perror(hostName);
        abort();
    }
    return sd;
}

int main()
{
    const std::string &hostName = "localhost";
    const int port = 3456;

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    SSL_library_init();

    SSL_CTX *ctx = InitCTX();
    int server = OpenConnection(hostName.c_str(), port);
    SSL *ssl = SSL_new(ctx);
    SSL_set_fd(ssl, server);
    if (SSL_connect(ssl) == -1) {
        ERR_print_errors_fp(stderr);
    }
    else {
        const int BUF_SIZE = 0x10000;
        std::string data;
        data.resize(BUF_SIZE);
        int n = sslRead(ssl, &data[0], BUF_SIZE);
        data.resize(n);

        sslWrite(ssl, &data[0], (int)data.size());
    }
}

您必须处理部分写入和部分读取。

对于 SSL_write(),您需要检查 return 值以确保您的数据已完全写入。如果您的数据只是部分写入,您需要再次调用 SSL_write() 以获取剩余数据。重复直到写入所有数据。

对于 SSL_read(),您需要继续阅读,直到获得零 return 值,然后检查是否正常关机。在执行此操作时,您需要附加到缓冲区并在接收数据时建立缓冲区。

更新:

评论中的@dascandy 是正确的。

您需要知道何时停止阅读以及处理部分阅读。单独关闭连接在您的情况下不起作用,因为当您关闭与 SSL 的连接时,您将失去在同一连接上发送回复的能力。 SSL 没有像 TCP 这样的 "half open" 连接。

一个合理的方法是在发送时为每条消息加上字节数作为前缀,在接收时首先读取字节数(使用任何方便的编码——固定长度,带分隔符的文本,等等)然后准确读取缓冲区中的字节数。