【C言語/C++】 TCP/IP通信をSSL/TLS暗号化して送受信するプログラム

この記事では,OpenSSLによる暗号化ソケット通信を行うC言語プログラムを紹介します.

目次

暗号化なし(平文)のソケット通信

以前にTCP/IPで送受信するプログラム(暗号化なし)に関して記載しました.

ソケット通信の中身を確認するにはパケットキャプチャツールであるtcpdumpコマンドで確認できます.

暗号化していないソケット通信の内容を確認してみましょう.平文のため,”Hello.from.server”という文字列が検出できてしまいます.


tcpdump -v -i lo -nn -X port 12345
...
    127.0.0.1.12345 > 127.0.0.1.42002: Flags [P.], cksum 0xfe39 (incorrect -> 0x2607), seq 1:18, ack 18, win 512, options [nop,nop,TS val 1292422716 ecr 1292422716], length 17
	0x0000:  4500 0045 f811 4000 4006 449f 7f00 0001  E..E..@.@.D.....
	0x0010:  7f00 0001 3039 a412 72b5 d817 764f 1c8b  ....09..r...vO..
	0x0020:  8018 0200 fe39 0000 0101 080a 4d08 ce3c  .....9......M..<
	0x0030:  4d08 ce3c 4865 6c6c 6f20 6672 6f6d 2073  M..<Hello.from.s
	0x0040:  6572 7665 72                             erver
...

tcpdumpがインストールされていない場合,下記コマンドでインストールできます.

・Debian系


apt install tcpdump

・RHEL系


dnf install tcpdump

このプログラムでは何の意味もない文字列を送信していますが,実際のソフトウェアでの運用では,個人情報認証情報など重要な情報をやりとりします.

これを見てわかるように平文のソケット通信がどれほど危険か理解できます.

暗号化したソケット通信プログラム

OpenSSLのインストール

SSL/TLS暗号化にはOpenSSLを使用します.

まず,OpenSSLの開発用パッケージをインストールします.

・Debian系


apt install libssl-dev

・RHEL系


dnf install openssl-devel

証明書と鍵の作成

下記コマンドで,暗号化で必要となる証明書と鍵を作成します.


openssl req -x509 -new -nodes -days 3650 -keyout private_key.pem -out certificate.crt

サーバプログラム


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include <openssl/err.h>
#include <openssl/ssl.h>

int main(int argc, char const *argv[])
{
    const int port_number = 12345;
    // create socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("Error: socket()\n");
        exit(EXIT_FAILURE);
    }
    // setup socket
    struct sockaddr_in server;
    int addrlen            = sizeof(server);
    server.sin_family      = AF_INET;
    server.sin_addr.s_addr = inet_addr("0.0.0.0");  // or INADDR_ANY
    server.sin_port        = htons(port_number);
    int opt                = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
    {
        perror("Error: setsockopt()");
        exit(EXIT_FAILURE);
    }
    if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
        perror("Error: bind()");
        exit(EXIT_FAILURE);
    }
    // start to listen requests
    if (listen(sockfd, SOMAXCONN) < 0)
    {
        perror("Error: listen");
        exit(EXIT_FAILURE);
    }

    // ssl
    // init ssl
    SSL_library_init();
    SSL_load_error_strings();
    // create ssl context
    SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
    if (!ctx)
    {
        perror("Error: SSL context\n");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    // load crt
    if (!SSL_CTX_use_certificate_file(ctx, "certificate.crt", SSL_FILETYPE_PEM))
    {
        perror("Error: SSL_CTX_use_certificate_file()\n");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    // load private key
    if (!SSL_CTX_use_PrivateKey_file(ctx, "private_key.pem", SSL_FILETYPE_PEM))
    {
        perror("Error: SSL_CTX_use_PrivateKey_file()\n");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    // accept request from client
    struct sockaddr_in client;
    int client_len = sizeof(client);
    int sock       = accept(sockfd, (struct sockaddr *)&client, (socklen_t *)&client_len);
    if (sock < 0)
    {
        perror("Error: accept()");
        exit(EXIT_FAILURE);
    }
    SSL *ssl = SSL_new(ctx);
    SSL_set_fd(ssl, sock);
    if (!SSL_accept(ssl))
    {
        perror("Error: SSL_accept()");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    // read data from client
    char buff[1024] = {0};
    SSL_read(ssl, buff, sizeof(buff));
    printf("recived message is \"%s\"\n", buff);
    // send data to client
    char *hello = "Hello from server";
    SSL_write(ssl, hello, strlen(hello));
    printf("Message sent to client\n");
    // close ssl
    SSL_shutdown(ssl);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    // close connecting socket
    close(sock);
    // close listening socket
    shutdown(sockfd, SHUT_RDWR);
    close(sockfd);
    return 0;
}

クライアントプログラム


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <arpa/inet.h>
#include <sys/socket.h>

#include <openssl/err.h>
#include <openssl/ssl.h>

int main(int argc, char const *argv[])
{
    const int port_number = 12345;
    int sock              = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("Error: socket()\n");
        exit(EXIT_FAILURE);
    }
    // setup socket
    struct sockaddr_in server;
    server.sin_family      = AF_INET;
    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_port        = htons(port_number);
    // connect to server
    if ((connect(sock, (struct sockaddr *)&server, sizeof(server))) < 0)
    {
        perror("Error: connect()\n");
        exit(EXIT_FAILURE);
    }
    // ssl
    // init ssl
    SSL_library_init();
    SSL_load_error_strings();
    // create ssl context
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
    if (!ctx)
    {
        perror("Error: SSL context\n");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    SSL *ssl = SSL_new(ctx);
    if (!SSL_set_fd(ssl, sock))
    {
        perror("Error: SSL_set_fd()\n");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    // SSL_connect(ssl);
    if (!SSL_connect(ssl))
    {
        perror("Error: SSL_connect()\n");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    // send data to server
    const char *hello = "Hello from client";
    SSL_write(ssl, hello, strlen(hello));
    printf("Message sent to server\n");
    // read data from server
    char buff[1024] = {0};
    SSL_read(ssl, buff, sizeof(buff));
    printf("recived message is \"%s\"\n", buff);
    // close ssl
    SSL_shutdown(ssl);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    // close connecting socket
    close(sock);
    return 0;
}

実行

サーバプログラムとクライアントプログラムを実行します


gcc -o server server.c -lssl -lcrypto
gcc -o client client.c -lssl -lcrypto

以前の記事と同様に,サーバを実行し,クライアントを実行すると下記のような結果が得られます.

・クライアント


./client 
Message sent to server
recived message is "Hello from server"

・サーバ


recived message is "Hello from client"
Message sent to client

ソケット通信を確認

実行時にソケット通信の内容を確認してみてください.

平文のソケット通信と違って内容が暗号化されていることが確認できます.


tcpdump -v -i lo -nn -X port 12345

参考にしたサイト

よかったらシェアしてね!
目次