この記事では,OpenSSLによる暗号化ソケット通信を行うC言語プログラムを紹介します.
目次
暗号化なし(平文)のソケット通信
以前にTCP/IPで送受信するプログラム(暗号化なし)に関して記載しました.
【C言語/C++】 TCP/IPで送受信を行うプログラム
ソケット通信とは ソケットは通信の出入口のようなものです.TCP/IPなどに代表されるHTTP通信などもソケット通信の一つです.すなわち,ソケットを使用することで異なる…
ソケット通信の中身を確認するにはパケットキャプチャツールである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
【C言語/C++】 TCP/IPで送受信を行うプログラム
ソケット通信とは ソケットは通信の出入口のようなものです.TCP/IPなどに代表されるHTTP通信などもソケット通信の一つです.すなわち,ソケットを使用することで異なる…
ソケット通信を確認
実行時にソケット通信の内容を確認してみてください.
平文のソケット通信と違って内容が暗号化されていることが確認できます.
tcpdump -v -i lo -nn -X port 12345