Linux下c语言实现socket+openssl数据传输加密

本文介绍了使用C语言实现Socket+SSL加密通信的方法。先对比普通Socket连接流程,为避免数据监听,引入SSL建立安全通道,阐述了其初始化流程、SSL环境及证书密钥的初始化,给出了Socket+SSL的C语言实现代码,最后用tcpdump检验,确认数据密文传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在进行网络编程的时候,我们通常使用socket进行数据的传输。然而socket作为一个数据传输协议,其本身对数据并不会作加密。所以数据传输的过程可以很轻松地被监听并截获到传输的数据。openssl提供了SSL的加密库,通过ssl+socket的方式可以保证连接安全和数据的加密。

1. Socket连接建立流程

在做socket加密之前,还是先与普通的socket做一个对比。

Client Server connect() accept() send(data) recv(data) send(response) recv(response) close() close() Client Server

上面的是我们通常在做一个socket连接的时候会涉及到的握手过程。服务端会通过accept去接收客户端的请求。客户端通过connect去连接到客户端。使用sendrecv去做数据的传输。那么传输过程中的参数data也就是我们交互的数据了。当我们建立连接开始发送数据的时候。使用一些抓包工具wireshark,或者tcpdump去监听socket端口就能很轻松地获取到传输的明文数据了。

2、Socket+SSL的初始化流程

所以为了避免数据的监听,我们就需要使用SSL去建立一个安全的通道。这里我们先把SSL的建立当作一个子流程

Client Server conn_fd=connect() accept_fd=accept() SSL Sub-Process Begins Initialize SSL Environment(准备证书,私钥) Initialize SSL Environment(准备证书,私钥) Create new SSL session (绑定conn_fd) Create new SSL session (绑定accept_fd) SSL_connect() (传入证书,私钥,和conn_fd进行握手) SSL_accept() (传入证书,私钥,和accept_fd进行握手) SSL Sub-Process Ends SSL_write(data) SSL_read(data) SSL_write(response) SSL_read(response) SSL Shutdown Sub-Process Begins SSL_shutdown() SSL_shutdown() SSL Shutdown Sub-Process Ends close() close() Client Server

SSL子过程主要插入在socket的connect()/accept()和数据交换之间。通过SSL建立完成的所以,一旦SSL握手完成,数据发送和接收的流程与普通的socket通信非常相似,只需要使用SSL_write()和SSL_read()来代替send()和recv()。c

替换 send()SSL_write(ssl, buffer, length)
替换recv()SSL_read(ssl, buffer, length)

3、初始化SSL环境,证书和密钥

  • openssl 生成证书(分别生成私钥和自签名证书),确保环境上已经安装完成了openssl
    • openssl genpkey -algorithm RSA -out key.pem
    • openssl req -new -x509 -key key.pem -out cert.pem -days 365
      在这里插入图片描述
      days为证书的有效期。命令执行完成后在当前目录下就会生成key.pemcert.pem 。记下文件路径,后续会作为参数传入到我们的函数中去。

4、Socket+SSL 的c语言实现

4.1 编写SSL连接函数

在进行SSL初始化的时候需要注意的是,由于连接协商的过程使用的是非对称加密,因此客户端和服务端在初始化的时候是使用的不同的算法。因此,我在函数中加入了一个SSL_MODE参数。用来指明当前是服务端还是客户端的SSL。

SSL* sync_initialize_ssl(const char* cert_path, const char* key_path, SSL_MODE mode, int fd) {
    const SSL_METHOD *method;
    SSL_CTX *ctx;
    SSL *ssl = NULL;
    // 初始化OpenSSL库
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();

    // 根据模式(客户端/服务端)选择合适的方法
    if (mode == SSL_MODE_SERVER) {
        method = SSLv23_server_method();
    } else if (mode == SSL_MODE_CLIENT) {
        method = SSLv23_client_method();
    } else {
        // 未知模式
        printf("Not found method");
        return NULL;
    }

    // 创建SSL上下文
    ctx = SSL_CTX_new(method);
    if (!ctx) {
        printf("Unable to create SSL context");
        return NULL;
    }

    // 配置SSL上下文
    if (SSL_CTX_use_certificate_file(ctx, cert_path, SSL_FILETYPE_PEM) <= 0 || SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0 ) {
        printf("Not found certificate or private key");
        SSL_CTX_free(ctx);
        return NULL;
    }
    // SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

    // 创建SSL对象
    ssl = SSL_new(ctx);
    if (!ssl) {
        printf("Failed to create SSL object.");
        return NULL;
    }

    // 设置文件描述符
    if (SSL_set_fd(ssl, fd) == 0) {
        printf("Failed to set fd to SSL object.");
        return NULL;
    }

    // SSL握手
    if ((mode == SSL_MODE_CLIENT && SSL_connect(ssl) <= 0) ||
        (mode == SSL_MODE_SERVER && SSL_accept(ssl) <= 0)) {
        int ssl_result;
        if (mode == SSL_MODE_CLIENT) {
            ssl_result = SSL_connect(ssl);
        } else if (mode == SSL_MODE_SERVER) {
            ssl_result = SSL_accept(ssl);
        }

        int ssl_err = SSL_get_error(ssl, ssl_result);

        const char *err_str;
        switch (ssl_err) {
            case SSL_ERROR_NONE:
                err_str = "No error";
                break;
            case SSL_ERROR_SSL:
                err_str = "Error in the SSL protocol";
                break;
            case SSL_ERROR_WANT_READ:
                err_str = "SSL read operation did not complete";
                break;
            case SSL_ERROR_WANT_WRITE:
                err_str = "SSL write operation did not complete";
                break;
            case SSL_ERROR_WANT_X509_LOOKUP:
                err_str = "SSL X509 lookup operation did not complete";
                break;
            case SSL_ERROR_SYSCALL:
                err_str = "Syscall error";
                break;
            case SSL_ERROR_ZERO_RETURN:
                err_str = "SSL connection was shut down cleanly";
                break;
            case SSL_ERROR_WANT_CONNECT:
                err_str = "SSL connect operation did not complete";
                break;
            case SSL_ERROR_WANT_ACCEPT:
                err_str = "SSL accept operation did not complete";
                break;
            default:
                err_str = "Unknown error";
                break;
        }
        printf("===============SSL handshake failed. Error: %s========!\n", err_str ? err_str : "Unknown");
        return NULL;
    }

    return ssl;
}

4.2 编写加密服务端server.c

#include <stdio.h>  
#include <stdlib.h>  
#include <stddef.h>
#include <string.h>  
#include <unistd.h>  
#include <sys/socket.h>  
#include <arpa/inet.h>
#include <netinet/in.h>  
#include <openssl/ssl.h>  
#include <openssl/err.h>  
  
#define SERVER_PORT 9990  
#define MAXLINE 4096  
  typedef enum {
    SSL_MODE_SERVER,
    SSL_MODE_CLIENT
} SSL_MODE;

int main(int argc, char **argv) {  
    int listenfd, connfd;  
    struct sockaddr_in servaddr, cliaddr;  
    char buf[MAXLINE];  

    // 创建监听套接字  
    listenfd = socket(AF_INET, SOCK_STREAM, 0);  
  
    // 绑定地址和端口  
    memset(&servaddr, 0, sizeof(servaddr));  
    servaddr.sin_family = AF_INET;  
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
    servaddr.sin_port = htons(SERVER_PORT);  
    bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));  
  
    // 监听连接  
    listen(listenfd, 10);  
    printf("Listening on port %d...\n", SERVER_PORT);  
  
    while (1) {  
        // 接受连接请求  
        socklen_t len = sizeof(cliaddr);  
        connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &len);  
        printf("Accepted connection from %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));  
  
        // 创建 SSL 对象并进行握手  
        SSL *ssl = sync_initialize_ssl("cert.pem", "key.pem", SSL_MODE_SERVER, connfd); 
  
        // 读取客户端发送的数据并回复  
        memset(buf, 0, MAXLINE);  
        SSL_read(ssl, buf, MAXLINE);  
        printf("Received: %s\n", buf);  
        SSL_write(ssl, "Hello, client!", strlen("Hello, client!"));  
  
        // 关闭连接和清理资源  
        close(connfd);  
        SSL_shutdown(ssl);  
        SSL_free(ssl);  
    }  
  

    return 0;  
}

4.3 编写加密客户端client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define SERVER_IP "127.0.0.1"  // 请根据需要更改服务器IP
#define SERVER_PORT 9990  
#define MAXLINE 4096  
  typedef enum {
    SSL_MODE_SERVER,
    SSL_MODE_CLIENT
} SSL_MODE;

int main(int argc, char **argv) {
    int sockfd;
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    // 创建 socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 设置服务器地址和端口
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    servaddr.sin_port = htons(SERVER_PORT);

    // 连接到服务器
    connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

    // 创建 SSL 对象并进行握手
    SSL *ssl = sync_initialize_ssl("cert.pem", "key.pem", SSL_MODE_CLIENT, sockfd); 
    // 发送数据给服务器
    SSL_write(ssl, "Hello, server!", strlen("Hello, server!"));

    // 读取服务器的回复
    memset(buf, 0, MAXLINE);
    SSL_read(ssl, buf, MAXLINE);
    printf("Received: %s\n", buf);

    // 关闭连接和清理资源
    close(sockfd);
    SSL_shutdown(ssl);
    SSL_free(ssl);

    return 0;
}


5、使用tcpdump检验

服务端和客户端编写完成后,分别运行起来,这里我运行的端口是9990。使用tcpdump抓取9990端口的传输数据
tcpdump -i any port 9990 -A

在这里插入图片描述
此时分别运行后,服务端客户端数据完成交互后,抓包所看到的数据已经是密文传输了。

源码获取

需要获取源码的可以关注公众号"一颗程序树",点击菜单栏的免费源码输入关键词socket即可

// C/C++, 利用OpenSSL库对Socket传输进行安全加密(RSA+AES) // 1. 利用RSA安全传输AES生成密钥所需的Seed(32字节) // 2. 利用AES_encrypt/AES_decrypt对Socket上面的业务数据进行AES加密/解密 // --- // * 理论上只需要AES就能保证全部流程,但由于AES加密所需要的AES-KEY是一个结构 // * 这个一个结构,如果通过网络进行传输,就需要对它进行网络编码,OpenSSL里面没有现成的API // * 所以就引入RSA来完成首次安全的传输,保证Seed不会被窃听 // * 同样,只使用RSA也能完成全部流程,但由于RSA的处理效率比AES低, // * 所以在业务数据传输加密上还是使用AES // --- // 下面的代码包含了上述传输加密流程所需的所有步骤(OpenSSL部分) // 在实际的Socket应用开发时,需要将这些步骤插入到Client/Server网络通信的特定阶段 // --- // 为能完成代码的编译和执行,需要先安装OpenSSL执行库及开发库 // 以Debian为例,需要安装openssl 和 libssl-dev // 编译命令: g++ -o rsa-encrypt rsa-encrypt.cpp -lcrypto // --- // 所需的OpenSSL主要的API及功能描述 // 1. RSA_generate_key() 随机生成一个RSA密钥对,供RSA加密/解密使用 // 2. i2d_RSAPublicKey() 将RSA密钥对里面的公钥提出到一个BUF,用于网络传输给对方 // 3. d2i_RSAPublicKey() 将从网络传过来的公钥信息生成一个加密使用的RSA(它里面只有公钥) // 4. RSA_public_encrypt() 使用RSA的公钥对数据进行加密 // 5. RSA_private_decrypt() 使用RSA的私钥对数据进行加密 // 6. AES_set_encrypt_key() 根据Seed生成AES密钥对中的加密密钥 // 7. AES_set_decrypt_key() 根据Seed生成AES密钥对中的解密密钥 // 8. AES_encrypt() 使用AES加密密钥对数据进行加密 // 9. AES_decrypt() 使用AES解密密钥对数据进行解密 // --- // 一个典型的安全Socket的建立流程, 其实就是如何将Server随机Seed安全发给Client // -- C: Client S:Server // C: RSA_generate_key() --> RSAKey --> i2d_RSAPublicKey(RSAKey) --> RSAPublicKey // C: Send(RSAPublicKey) TO Server // S: Recv() --> RSAPublicKey --> d2i_RSAPublicKey(RSAPublicKey) --> RSAKey // S: Rand() --> Seed --> RSA_public_encrypt(RSAKey, Seed) --> EncryptedSeed // S: Send(EncryptedSeed) TO Client // C: Recv() --> EncryptedSeed --> RSA_private_decrypt(RSAKey, EncryptedSeed) --> Seed // --- 到此, Client和Server已经完成完成传输Seed的处理 // --- 后面的流程是它们怎样使用这个Seed来进行业务数据的安全传输 // C: AES_set_encrypt_key(Seed) --> AESEncryptKey // C: AES_set_decrypt_key(Seed) --> AESDecryptKey // S: AES_set_encrypt_key(Seed) --> AESEncryptKey // S: AES_set_decrypt_key(Seed) --> AESDecryptKey // --- Client传输数据给Server // C: AES_encrypt(AESEncryptKey, Data) --> EncryptedData --> Send() --> Server // S: Recv() --> EncryptedData --> AES_decrypt(AESDecryptKey, EncryptedData) --> Data // --- Server传输数据给Client // S: AES_encrypt(AESEncryptKey, Data) --> EncryptedData --> Send() --> Client // C: Recv() --> EncryptedData --> AES_decrypt(AESDecryptKey, EncryptedData) --> Data / ========================================================================================= */
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Demonslzh6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值