TCP通信及基于OpenSSL实现TLS

Socket

当通信过程不相关且不共享文件系统时,可以采用何种机制?这就需要一个通用的传输层通信子系统驻留在每个参与通信的主机上,如TCP/IPTCP/IP是Internet上使用的通用消息传输协议族。该子系统将网络上的一个端点的消息路由并传递到另一个端点。Unix套接字接口是一种将程序的地址空间(a socket)中的引用对象与底层消息传输子系统中的通信端点(a port)绑定的方式,以实现在网络软件连接的端点之间进行消息传递。

在网络通信中,服务器首先创建一个本地套接字,并将其绑定到通信子系统中唯一的端口地址。然后,它告诉通信,使这成为一个侦听端口,以便它可以创建一个相关的请求队列。然后,服务器等待进入的连接。客户端创建一个本地套接字,并要求通信子系统使用服务器的已知端口号将其套接字连接到远程服务器。每个被接受的连接建立一个新的临时服务器端套接字,用于处理特定的客户机/服务器交换。例如,允许多个线程使用相同的服务器端口号处理与客户端的独立连接。

连接被接受和建立后,通信通过写入客户端的本地套接字进行,数据通过网络被TCP/IP透明地携带,并可通过另一端的服务器套接字接收。服务器解析并作用于请求,然后可以将响应写入自己的套接字并关闭连接。然后将响应返回给客户端在TCP/IP协议下,客户端将从其套接字中读取响应,然后关闭其侧的连接。

实现消息交换的Socket接口的基本调用序列。

实现客户服务器消息交换的Socket接口的基本调用序列。

TCP

TCP 服务端步骤

记得配置好gcc,gcc使用可参考:gcc与Makefile

/*************** Server Code ****************/

#include <stdio.h>      /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), bind(), and connect() */
#include <arpa/inet.h>  /* for sockaddr_in and inet_ntoa() */
#include <stdlib.h>     /* for atoi() and exit() */
#include <string.h>     /* for memset() */
#include <unistd.h>     /* for close() */

#define MAXPENDING 5    /* Maximum outstanding connection requests */
#define RCVBUFSIZE 32   /* Size of receive buffer */

int main(int argc, char *argv[])
{
    int servSock;               /* Socket descriptor for server */
    int clntSock;               /* Socket descriptor for client */
    struct sockaddr_in echoServAddr; /* Local address */
    struct sockaddr_in echoClntAddr; /* Client address */
    unsigned short echoServPort;   /* Server port */
    unsigned int clntLen;       /* Length of client address data structure */
    char echoBuffer[RCVBUFSIZE];    /* Buffer for receiving client's msg string */
    int recvMsgSize;            /* Size of received message */
    char *echoString;           /* Server's reply to client */
    unsigned int echoStringLen;     /* Length of server's reply string */

    echoString = "server is alive, how are you?"; /* Server's reply to client */
    echoStringLen = 29;

    echoServPort = 23; /* local port on which server is going to listen */

    /* Create local TCP/IP socket for incoming connections */
    servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    /* Construct local address structure */
    memset(&echoServAddr, 0, sizeof(echoServAddr)); /* Zero out structure */
    echoServAddr.sin_family = AF_INET; /* Internet address family */
    echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */
    echoServAddr.sin_port = htons(echoServPort); /* Local port */

    /* Bind local socket to the desired server port address */
    int flag = bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr));
    if (flag == -1) {
        printf("error");
        return 0;
    }

    /* Mark the socket so it will listen for incoming connections */
    listen(servSock, MAXPENDING);

    for (;;) /* Run forever */
    {
        /* Set the size of the in-out parameter */
        clntLen = sizeof(echoClntAddr);

        /* Blocking wait for a client to connect */
        clntSock = accept(servSock, (struct sockaddr *) &echoClntAddr, &clntLen);

        /* clntSock is connected to a client! */
        printf("Server: Handling client %s\n", inet_ntoa(echoClntAddr.sin_addr));

        /* Receive message from client */
        recvMsgSize = recv(clntSock, echoBuffer, RCVBUFSIZE, 0);
        printf("Server: Received msg-> %s\n", echoBuffer);

        /* Send response message back to client */
        send(clntSock, echoString, echoStringLen, 0);
        printf("Server: Sent Reply-> %s\n", echoString);

        close(clntSock); /* Close client socket */
    }

    return 0;
}

TCP 客户端步骤

/**************** Client Code ***************/

#include <stdio.h>      /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), connect(), send(), and recv() */
#include <arpa/inet.h>  /* for sockaddr_in and inet_addr() */
#include <stdlib.h>     /* for atoi() and exit() */
#include <string.h>     /* for memset() */
#include <unistd.h>     /* for close() */

#define RCVBUFSIZE 32   /* Size of receive buffer */

int main(int argc, char *argv[])
{
    int sock;               /* Socket descriptor */
    struct sockaddr_in echoServAddr; /* Echo server address */
    unsigned short echoServPort = 23; /* Echo server port */
    char *servIP;           /* Server IP address (dotted quad) */
    char *echoString;       /* String to send to echo server */
    char echoBuffer[RCVBUFSIZE];    /* Buffer for echo string */
    unsigned int echoStringLen;     /* Length of string to echo */
    int bytesRcvd, totalBytesRcvd;  /* Bytes read in single recv() and total bytes read */

    servIP = "10.0.65.0"; /* server IP address (this host's own address) */
    echoString = "hello"; /* string to echo */
    echoStringLen = 5;    /* Length of string to echo */

    /* Create a local client stream socket using TCP */
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    /* Construct the server address structure */
    memset(&echoServAddr, 0, sizeof(echoServAddr));    /* Zero out structure */
    echoServAddr.sin_family = AF_INET;                 /* Internet address family */
    echoServAddr.sin_addr.s_addr = inet_addr(servIP);  /* Server IP address */
    echoServAddr.sin_port = htons(echoServPort);       /* Server port */

    /* Establish the connection to the server */
    connect(sock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr));

    /* send the message over the socket connection */
    send(sock, echoString, echoStringLen, 0);
    printf("Client: Sent greeting-> %s\n", echoString);

    /* Receive a reply back from the server */
    /* Receive up to the buffer size (minus 1 to leave space for a null terminator) bytes from the sender */
    bytesRcvd = recv(sock, echoBuffer, RCVBUFSIZE - 1, 0);
    echoBuffer[bytesRcvd] = '\0'; /* Terminate the string! */
    printf("Client: Received Reply-> %s\n", echoBuffer); /* Print the string received from server */

    close(sock);
    exit(0);
}

之后如果有需要会发出Windows下Java版本的TCP/IP,并且Java版本也有TLS协议的文章参考:SSL/TLS协议详解以及配置实战

最终实现

最终实现

TLS

TCP SSL 服务端步骤

  1. 先附上Windows下的参考文章:基于openssl实现TCP双向认证
  2. openssl的版本,本文用的1.0.2p,这个版本支持 TLSv1.2,但不支持 TLSv1.3。所以在使用时应该选择相应的加密套件和协议版本,以确保与其他设备和服务的兼容性。
  3. 要先生成CA的私钥和证书:SSL/CA 证书及其相关证书文件(pem、crt、cer、key、csr)

生成CA的私钥

openssl genrsa -out server.key 2048

生成CA的请求文件

openssl req -new -key server.key -out server.csr

生成证书

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

上面三步完成后,在文件夹下会得到三个文件:

server.key
server.csr
server.crt

  1. 查看生成的密钥和证书信息,参考:ubuntu用openssl生成私钥和证书步骤
  2. 记得注意密钥和证书的位置和权限,路径要放在同个文件夹,一开始没配置好花了很长时间。

gcc编译

gcc -o server server.c -lssl -lcrypto

#include <stdio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define MAXPENDING 5
#define RCVBUFSIZE 1024

int main() {
    int servSock, clntSock;
    struct sockaddr_in echoServAddr, echoClntAddr;
    unsigned short echoServPort = 23;
    char echoBuffer[RCVBUFSIZE] = "";
    char echoString[1024] = "server is alive, how are you?";
    unsigned int echoStringLen = strlen(echoString);
    SSL_CTX *ctx;
    SSL *ssl;

    SSL_library_init();
    //1.0.2p这个版本支持 TLSv1.2,但不支持 TLSv1.3。所以在使用时应该选择相应的加密套件和协议版本,以确保与其他设备和服务的兼容性。
    ctx = SSL_CTX_new(TLSv1_2_method());

    // Load certificate
    SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM);

    // Load private key
    SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM);

    if (!SSL_CTX_check_private_key(ctx)) {
        printf("SSL_CTX_check_private_key failed\n");
        return 0;
    }

    SSL_CTX_set_timeout(ctx, 100);

    servSock = socket(AF_INET, SOCK_STREAM, 0);
    if (servSock == -1) {
        printf("socket create failed\n");
        return 0;
    }

    memset(&echoServAddr, 0, sizeof(echoServAddr));
    echoServAddr.sin_family = AF_INET;
    echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    echoServAddr.sin_port = htons(echoServPort);

    if (bind(servSock, (struct sockaddr *)&echoServAddr, sizeof(echoServAddr)) == -1) {
        printf("socket bind failed\n");
        return 0;
    }

    if (listen(servSock, MAXPENDING) == -1) {
        printf("socket listen failed\n");
        return 0;
    }

    printf("tcp ssl server:%d\n", echoServPort);
    for (;;) {
        unsigned int clntLen = sizeof(echoClntAddr);
        clntSock = accept(servSock, (struct sockaddr *)&echoClntAddr, &clntLen);
        printf("Server: Handling client %s\n", inet_ntoa(echoClntAddr.sin_addr));

        SSL* ssl = SSL_new(ctx);
        if (ssl == NULL) {
            printf("socket:%d,SSL_new failed\n", clntSock);
            goto freessl;
        }

        SSL_set_fd(ssl, clntSock);

        if (SSL_accept(ssl) < 0) {
            printf("socket:%d,SSL_accept failed\n", clntSock);
            goto freessl;
        }

        SSL_read(ssl, echoBuffer, sizeof(echoBuffer));
        printf("Server: Received msg-> %s\n", echoBuffer);

        SSL_write(ssl, echoString, echoStringLen);
        printf("Server: Sent Reply-> %s\n", echoString);

    freessl:
        SSL_shutdown(ssl);
        close(clntSock);

        // Free SSL
        if (ssl)
            SSL_free(ssl);

        continue;
    }

    return 0;
}

TCP SSL 客户端步骤

这里写了很多debug的代码,本文编辑之初衷就是自己花了一天半在解决bug。

#include <stdio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define RCVBUFSIZE 1024

int main() {
    int sock;
    struct sockaddr_in server;
    char message[1024] = "hello";
    char buffer[RCVBUFSIZE] = "";
    SSL_CTX *ctx;
    SSL *ssl;
    int res = 0;

    SSL_library_init();
    ctx = SSL_CTX_new(TLSv1_2_method());

    sock = socket(AF_INET, SOCK_STREAM, 0);
    server.sin_addr.s_addr = inet_addr("10.0.65.0");
    server.sin_family = AF_INET;
    server.sin_port = htons(23);

    if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == -1) {
        printf("socket connect failed\n");
    }

    printf("connect client, socket:%d\n", sock);

    ssl = SSL_new(ctx);
    if (ssl == NULL) {
        printf("socket:%d, SSL_new failed\n", sock);
        goto freessl;
    }

    SSL_set_fd(ssl, sock);

    if (SSL_connect(ssl) < 0) {
        printf("socket:%d, SSL_accept failed\n", sock);
        goto freessl;
    }
    printf("ssl connected ok\n");

    int result = SSL_write(ssl, message, strlen(message));
    if (result <= 0) {
        int error = SSL_get_error(ssl, result);
        switch (error) {
            case SSL_ERROR_ZERO_RETURN:
                printf("SSL_write error: Connection closed\n");
                break;
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
                printf("SSL_write error: Try again later\n");
                break;
            case SSL_ERROR_SYSCALL:
                perror("SSL_write error");
                break;
            default:
                printf("SSL_write error: %d\n", error);
                break;
        }
    }

    SSL_read(ssl, buffer, sizeof(buffer));
    printf("Client: Received Reply-> %s\n", buffer);

freessl:
    SSL_shutdown(ssl);
    close(sock);

    // Free SSL
    if (ssl)
        SSL_free(ssl);

    return 0;
}

最终实现

我这边是通过交叉编译在通过putty访问远程服务器进行实现,具体代码的ip和端口需要自行配置。本地ip:127.0.0.1,port:8080。
tips1:端口不要和putty的冲突了。我putty是22,这里就配置的23。
tips2:在深入学习后发现,TLS协议最好使用443端口。
最终实现

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
OpenSSL是一个开源的软件库,它提供了SSL和TLS协议的实现,用于加密通信。在C语言中使用OpenSSL库来实现TLS的话,你可以按照以下步骤进行操作: 1. 安装OpenSSL库:首先,需要从OpenSSL官网下载并安装OpenSSL库。根据你的操作系统和编译环境选择合适的版本。 2. 包含头文件:在你的C代码中,包含OpenSSL的头文件,以便能够使用库中提供的函数和数据结构。通常包含的头文件是`<openssl/ssl.h>`和`<openssl/crypto.h>`。 3. 创建SSL上下文:使用`SSL_CTX_new()`函数创建一个SSL上下文对象,用于配置和管理TLS连接。 4. 配置TLS选项:通过调用`SSL_CTX_set_options()`函数设置TLS连接的选项。例如,你可以启用服务器验证、禁用SSLv2和SSLv3等等。 5. 加载证书和私钥:为了建立TLS连接,你需要加载服务器证书和私钥。可以使用`SSL_CTX_use_certificate_file()`和`SSL_CTX_use_PrivateKey_file()`函数从文件中加载证书和私钥。 6. 建立连接:使用`SSL_new()`函数创建一个SSL对象,并通过`SSL_set_fd()`函数将其与一个TCP socket绑定。 7. 握手过程:调用`SSL_accept()`函数进行TLS握手。在握手过程中,服务器和客户端将协商加密算法、密钥交换等细节。 8. 数据传输:握手成功后,可以使用`SSL_read()`和`SSL_write()`函数进行安全的数据传输。这些函数与标准的`read()`和`write()`函数类似,但会自动进行加密和解密操作。 9. 关闭连接:使用`SSL_shutdown()`函数关闭TLS连接,并释放相关资源。记得在退出程序之前,调用`SSL_CTX_free()`函数释放SSL上下文对象。 请注意,上述步骤仅提供了一般性的概述,实际使用时可能需要更多的代码和配置。具体的实现细节可以参考OpenSSL的官方文档和示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值