This commit is contained in:
Zheyuan Wu
2026-03-02 21:18:02 -06:00
commit 4627f0db5c
17 changed files with 1484 additions and 0 deletions

7
.env Normal file
View File

@@ -0,0 +1,7 @@
NET_SUBNET=172.30.0.0/24
SERVER_IP=172.30.0.10
CLIENT_IP=172.30.0.11
SERVER_PORT=3030
INITIAL_VECTOR=0783F40FBD09EAE9AA3E5F295414074E
SECRET_KEY=F57D76F79AEE1CDDB399865653E1871E2E23619C1ADA333AEDBA387C0E5472A2
ADD=whatever metadata you want authenticated

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# latex garbages
*.aux
*.fdb_latexmk
*.fls
*.log
*.synctex.gz
*.out
# vscode garbage
.vscode

Binary file not shown.

BIN
bin/client Executable file

Binary file not shown.

BIN
bin/server Executable file

Binary file not shown.

1
client-log.sh Normal file
View File

@@ -0,0 +1 @@
docker logs hw4-client

246
client.cpp Normal file
View File

@@ -0,0 +1,246 @@
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// open ssl
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <errno.h> // error handling
#include <unistd.h> // for close()
#include <string>
#include <cstring>
#include <cstdlib> // for atoi()
#include <vector>
#include "helper.h"
// GCM encryption
int gcm_encrypt(unsigned char *plaintext, int plaintext_len,
unsigned char *aad, int aad_len,
unsigned char *key,
unsigned char *iv, int iv_len,
unsigned char *ciphertext,
unsigned char *tag)
{
EVP_CIPHER_CTX *ctx;
int len, ciphertext_len;
/* Create and initialize the context */
ctx = EVP_CIPHER_CTX_new();
if (!ctx)
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Initialize the encryption operation. */
if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Set IV length if default 12 bytes (96 bits) is not appropriate
*/
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
};
/* Initialize key and IV */
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Provide any AAD data. This can be called zero or more times as
* required
*/
if (!EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Provide the message to be encrypted, and obtain the encrypted
output.
* EVP_EncryptUpdate can be called multiple times if necessary
*/
if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
ciphertext_len = len;
/*
* Finalize the encryption. Normally ciphertext bytes may be written at
* this stage, but this does not occur in GCM mode
*/
if (!EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
ciphertext_len += len;
/* Get the tag */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
int main(void)
{
load_env(".env");
// Declare variables
const char *server_ip = std::getenv("SERVER_IP");
const int server_port = std::atoi(std::getenv("SERVER_PORT"));
printf("Connecting to server %s:%d\n", server_ip, server_port);
char client_message[1024];
char server_message[1024];
const char *custom_message = "Zheyuan Wu: ";
// encryption parameters
const char *inital_vector = std::getenv("INITIAL_VECTOR");
const char *secret_key = std::getenv("SECRET_KEY");
const char *add = std::getenv("ADD");
unsigned char key_bytes[32], iv_bytes[16];
int key_len = hex_to_bytes_upper(secret_key, key_bytes, sizeof(key_bytes));
int iv_len = hex_to_bytes_upper(inital_vector, iv_bytes, sizeof(iv_bytes));
if (key_len != 32 || iv_len != 16)
{
fprintf(stderr, "Invalid key/IV size for AES-256-GCM\n");
return 1;
}
// Create socket:
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1)
{
perror("Failed to create socket");
return 1;
}
else
{
printf("Socket created successfully\n");
}
// Send connection request to server, be sure to set por tand IP the same as server-side
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
perror("Failed to connect to server");
close(client_socket);
return 1;
}
else
{
printf("Connected to server successfully\n");
}
// Get input from the user:
printf("Enter message sent to the server (type \\quit to exit): ");
// clean the buffer
memset(client_message, 0, sizeof(client_message));
if (fgets(client_message, sizeof(client_message), stdin) == NULL)
{
// EOF or error reading from stdin, exit the loop
perror("Error reading from stdin");
return 1;
}
while (strcmp(client_message, "\\quit\n") != 0)
{
// Send the message to server:
// add my name in the front
char buffer[2048];
std::snprintf(buffer, sizeof(buffer), "%s%s", custom_message, client_message);
printf("Message sent to server: %s, length: %d \n", buffer, (int)strlen(buffer));
unsigned char tag[16] = {0};
unsigned char cipher_text[2048] = {0};
int cipher_text_len = gcm_encrypt((unsigned char *)buffer,
(int)strlen(buffer),
(unsigned char *)add,
(int)strlen(add),
key_bytes,
iv_bytes,
iv_len,
cipher_text,
tag);
printf("Ciphertext sent to server: %s\n", byte_to_hex(cipher_text, cipher_text_len));
ssize_t sent = send(client_socket, cipher_text, cipher_text_len, 0);
if (sent <= 0)
{
perror("No message sent to server");
break;
}
sleep(1);
// send tag of the message
printf("TAG sent to server: %s\n", byte_to_hex(tag, 16));
ssize_t sent_tag = send(client_socket, tag, 16, 0);
if (sent_tag <= 0)
{
perror("No TAG sent to server");
break;
}
// Receive the server's response:
// add terminator for string
ssize_t recvd = recv(client_socket, server_message, sizeof(server_message) - 1, 0);
if (recvd <= 0)
{
perror("No message received from server");
break;
}
server_message[recvd] = '\0';
printf("Server's response: %s\n", server_message);
printf("Enter message sent to the server (type \\quit to exit): ");
// clean the buffer
memset(client_message, 0, sizeof(client_message));
memset(server_message, 0, sizeof(server_message));
if (fgets(client_message, sizeof(client_message), stdin) == NULL)
{
// EOF or error reading from stdin, exit the loop
perror("Error reading from stdin");
break;
}
}
// Close the socket
close(client_socket);
return 0;
}

30
compile.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
mkdir -p bin
# add libssl-dev if needed
# apt-get update && apt-get install -y --no-install-recommends libssl-dev
# Adjust flags as you like
CXXFLAGS="-std=c++17 -O2 -Wall -Wextra -pedantic"
OPENSSL_LIBS="-lssl -lcrypto"
g++ ${CXXFLAGS} server.cpp helper.cpp -o bin/server ${OPENSSL_LIBS}
g++ ${CXXFLAGS} client.cpp helper.cpp -o bin/client ${OPENSSL_LIBS}
echo "Built:"
file bin/server bin/client || true
chmod a+x bin/server bin/client
# stop the containers and free up network resources
docker compose down --remove-orphans || true
docker network rm net-hw4 >/dev/null 2>&1 || true
# start the containers
docker compose up -d server
docker compose run --rm -it client
echo "Done."

38
docker-compose.yml Normal file
View File

@@ -0,0 +1,38 @@
services:
server:
image: debian:bookworm-slim
container_name: hw4-server
working_dir: /app
env_file:
- .env
networks:
net-hw4:
ipv4_address: ${SERVER_IP}
volumes:
- ./bin/server:/usr/local/bin/server:ro
entrypoint: ["bash", "-lc", "apt-get update && apt-get install -y --no-install-recommends libstdc++6 iproute2 libssl-dev; exec stdbuf -oL -eL /usr/local/bin/server"]
client:
image: debian:bookworm-slim
container_name: hw4-client
working_dir: /app
env_file:
- .env
networks:
net-hw4:
ipv4_address: ${CLIENT_IP}
depends_on:
- server
stdin_open: true
tty: true
volumes:
- ./bin/client:/usr/local/bin/client:ro
entrypoint: ["bash", "-lc", "apt-get update && apt-get install -y --no-install-recommends libstdc++6 iproute2 libssl-dev; exec stdbuf -oL -eL /usr/local/bin/client"]
networks:
net-hw4:
name: net-hw4
driver: bridge
ipam:
config:
- subnet: ${NET_SUBNET}

60
helper.cpp Normal file
View File

@@ -0,0 +1,60 @@
// load environment variables from .env file
#include <fstream>
#include <cstdlib>
#include <string>
#include <cstring>
void load_env(char const* path)
{
std::ifstream f(path);
std::string line;
while (std::getline(f, line))
{
if (line.empty() || line[0] == '#')
continue;
auto pos = line.find('=');
if (pos == std::string::npos)
continue;
std::string key = line.substr(0, pos);
std::string val = line.substr(pos + 1);
#ifdef _WIN32
_putenv_s(key.c_str(), val.c_str());
#else
setenv(key.c_str(), val.c_str(), 1);
#endif
}
}
// hex to byte helper function
int hex_to_bytes_upper(const char *hex, unsigned char *out, size_t out_size)
{
size_t i = 0;
while (hex[0] && hex[1])
{
if (i >= out_size)
return -1;
unsigned char h = hex[0];
unsigned char l = hex[1];
int hi = (h <= '9') ? (h - '0') : (h - 'A' + 10);
int lo = (l <= '9') ? (l - '0') : (l - 'A' + 10);
// minimal sanity check
if (hi < 0 || hi > 15 || lo < 0 || lo > 15)
return -1;
out[i++] = (unsigned char)((hi << 4) | lo);
hex += 2;
}
return (int)i;
}
char * byte_to_hex(unsigned char *bytes, size_t len)
{
char *hex = new char[len * 2 + 1];
for (size_t i = 0; i < len; i++)
{
sprintf(hex + i * 2, "%02X", bytes[i]);
}
hex[len * 2] = '\0';
return hex;
}

9
helper.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef HELPER_H
#define HELPER_H
void load_env(char const* path);
int hex_to_bytes_upper(const char* hex, unsigned char* out, size_t out_size);
char* byte_to_hex(unsigned char* bytes, size_t len);
void cal_hmac(unsigned char* mac, char* message);
#endif

Binary file not shown.

View File

@@ -0,0 +1,811 @@
\documentclass[11pt]{article}
\usepackage{amsmath, amsfonts, amsthm}
\usepackage{amssymb}
\usepackage{fancyhdr,parskip}
\usepackage{fullpage}
\usepackage{mathrsfs}
\usepackage{mathtools}
\usepackage{float}
% code listing
\usepackage{listings}
\usepackage{xcolor}
\lstset{%
language=c++,
breaklines=true,
commentstyle=\color{green}, % comment style
escapeinside={\%*}{*)}, % if you want to add LaTeX within your code
keywordstyle=\color{blue}, % keyword style
stringstyle=\color{purple}, % string literal style
}
%%
%% Stuff above here is packages that will be used to compile your document.
%% If you've used unusual LaTeX features, you may have to install extra packages by adding them to this list.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\setlength{\headheight}{15.2pt}
\setlength{\headsep}{20pt}
\pagestyle{fancyplain}
%%
%% Stuff above here is layout and formatting. If you've never used LaTeX before, you probably don't need to change any of it.
%% Later, you can learn how it all works and adjust it to your liking, or write your own formatting code.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%
% These commands create theorem-like environments.
\newtheorem{theorem}{Theorem}
\newtheorem{lemma}[theorem]{Lemma}
\newtheorem{corollary}[theorem]{Corollary}
\newtheorem{prop}[theorem]{Proposition}
\newtheorem{defn}[theorem]{Definition}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This section contains some useful macros that will save you time typing.
%%
% Using \displaystyle (or \ds) in a block of math has a number of effects, but most notably, it makes your fractions come out bigger.
\newcommand{\ds}{\displaystyle}
% These lines are for displaying integrals; typing \dx will make the dx at the end of the integral look better.
\newcommand{\is}{\hspace{2pt}}
\newcommand{\dx}{\is dx}
% These commands produce the fancy Z (for the integers) and other letters conveniently.
\newcommand{\Z}{\mathbb{Z}}
\newcommand{\Q}{\mathbb{Q}}
\newcommand{\R}{\mathbb{R}}
\newcommand{\C}{\mathbb{C}}
\newcommand{\F}{\mathbb{F}}
\newcommand{\T}{\mathcal{T}}
\newcommand{\B}{\mathcal{B}}
% for fancy empty set char
\renewcommand{\emptyset}{\varnothing}
% customized commands for future assignements
\newcommand{\imply}{\Rightarrow}
\def\P{\mathscr{P}}
\def\L{\mathscr{L}}
\def\M{\mathscr{M}}
\DeclarePairedDelimiterX{\inp}[2]{\langle}{\rangle}{#1, #2}
% url embedding
\usepackage{hyperref}
\hypersetup{colorlinks=true,linkcolor=blue,urlcolor=blue}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This is the header. It will appear on every page, and it's a good place to put your name, the assignment title, and stuff like that.
%% I usually leave the center header blank to avoid clutter.
%%
\fancyhead[L]{\textbf{CSE4303 Homework 4}}
\fancyhead[C]{509191}
\fancyhead[R]{Zheyuan Wu}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{document}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Actual math starts here!
% Use an enumerated list to write up problems. First we begin a list.
\textit{Source code and detailed setup scripts are available at \url{https://git.trance-0.com/Trance-0/CSE4303H4}.}
\begin{enumerate}
\item[1.] Description of experimental setup
We used the same docker setup as the previous assignment.
\begin{lstlisting}[language=bash]
services:
server:
image: debian:bookworm-slim
container_name: hw4-server
working_dir: /app
env_file:
- .env
networks:
net-hw4:
ipv4_address: ${SERVER_IP}
volumes:
- ./bin/server:/usr/local/bin/server:ro
entrypoint: ["bash", "-lc", "apt-get update && apt-get install -y --no-install-recommends libstdc++6 iproute2 libssl-dev; exec stdbuf -oL -eL /usr/local/bin/server"]
client:
image: debian:bookworm-slim
container_name: hw4-client
working_dir: /app
env_file:
- .env
networks:
net-hw4:
ipv4_address: ${CLIENT_IP}
depends_on:
- server
stdin_open: true
tty: true
volumes:
- ./bin/client:/usr/local/bin/client:ro
entrypoint: ["bash", "-lc", "apt-get update && apt-get install -y --no-install-recommends libstdc++6 iproute2 libssl-dev; exec stdbuf -oL -eL /usr/local/bin/client"]
networks:
net-hw4:
name: net-hw4
driver: bridge
ipam:
config:
- subnet: ${NET_SUBNET}
\end{lstlisting}
Here is the environment file used for this assignment.
\begin{lstlisting}[language=bash]
NET_SUBNET=172.30.0.0/24
SERVER_IP=172.30.0.10
CLIENT_IP=172.30.0.11
SERVER_PORT=3030
INITIAL_VECTOR=0783F40FBD09EAE9AA3E5F295414074E
SECRET_KEY=F57D76F79AEE1CDDB399865653E1871E2E23619C1ADA333AEDBA387C0E5472A2
ADD=whatever metadata you want authenticated
\end{lstlisting}
Here is the \texttt{helper.cpp} we used for this assignment.
\begin{lstlisting}[language=c++]
// load environment variables from .env file
#include <fstream>
#include <cstdlib>
#include <string>
#include <cstring>
void load_env(char const* path)
{
std::ifstream f(path);
std::string line;
while (std::getline(f, line))
{
if (line.empty() || line[0] == '#')
continue;
auto pos = line.find('=');
if (pos == std::string::npos)
continue;
std::string key = line.substr(0, pos);
std::string val = line.substr(pos + 1);
#ifdef _WIN32
_putenv_s(key.c_str(), val.c_str());
#else
setenv(key.c_str(), val.c_str(), 1);
#endif
}
}
// hex to byte helper function
int hex_to_bytes_upper(const char *hex, unsigned char *out, size_t out_size)
{
size_t i = 0;
while (hex[0] && hex[1])
{
if (i >= out_size)
return -1;
unsigned char h = hex[0];
unsigned char l = hex[1];
int hi = (h <= '9') ? (h - '0') : (h - 'A' + 10);
int lo = (l <= '9') ? (l - '0') : (l - 'A' + 10);
// minimal sanity check
if (hi < 0 || hi > 15 || lo < 0 || lo > 15)
return -1;
out[i++] = (unsigned char)((hi << 4) | lo);
hex += 2;
}
return (int)i;
}
char * byte_to_hex(unsigned char *bytes, size_t len)
{
char *hex = new char[len * 2 + 1];
for (size_t i = 0; i < len; i++)
{
sprintf(hex + i * 2, "%02X", bytes[i]);
}
hex[len * 2] = '\0';
return hex;
}
\end{lstlisting}
\newpage
\item[2.] Server code
\begin{enumerate}
\item Screenshot or well-formatted copy of code.
Here is the server code used for this assignment.
\begin{lstlisting}[language=c++]
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// open ssl
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <errno.h> // for perror()
#include <unistd.h> // for close()
#include <string>
#include <cstring>
#include <cstdlib> // for atoi()
#include <vector>
// load environment variables from .env file, and additional helper functions
#include "helper.h"
using std::string;
using std::vector;
int gcm_decrypt(unsigned char *ciphertext, int ciphertext_len,
unsigned char *aad, int aad_len,
unsigned char *tag,
unsigned char *key,
unsigned char *iv, int iv_len,
unsigned char *plaintext)
{
EVP_CIPHER_CTX *ctx;
int len, plaintext_len, ret;
/* Create and initialize the context */
ctx = EVP_CIPHER_CTX_new();
if (!ctx)
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Initialize the decryption operation. */
if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Initialize key and IV */
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Provide any AAD data. This can be called zero or more times as
* required
*/
if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Provide the message to be decrypted, and obtain the plaintext
output.
* EVP_DecryptUpdate can be called multiple times if necessary
*/
if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
plaintext_len = len;
/* Set expected tag value. Works in OpenSSL 1.0.1d and later */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Finalize the decryption. A positive return value indicates success,
* and anything else is a failure - the plaintext is not trustworthy.
*/
ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
if (ret > 0)
{
/* Success */
plaintext_len += len;
return plaintext_len;
}
else
{
/* Verify failed */
return 0;
}
}
int main(void)
{
printf("Server starting...\n");
load_env(".env");
// Declare variables
const char *server_ip = getenv("SERVER_IP");
const int server_port = atoi(getenv("SERVER_PORT"));
char client_message[2048];
char server_message[2048];
const char *custom_message_success = "Server: Hello from server, message authenticated!\n";
const char *custom_message_failed = "Server: Hello from server, message failed authentication!\n";
// debug
printf("Server starting at IP: %s, Port: %d\n", server_ip, server_port);
// encryption parameters
const char *inital_vector = std::getenv("INITIAL_VECTOR");
const char *secret_key = std::getenv("SECRET_KEY");
const char *add = std::getenv("ADD");
unsigned char key_bytes[32], iv_bytes[16];
int key_len = hex_to_bytes_upper(secret_key, key_bytes, sizeof(key_bytes));
int iv_len = hex_to_bytes_upper(inital_vector, iv_bytes, sizeof(iv_bytes));
if (key_len != 32 || iv_len != 16)
{
fprintf(stderr, "Invalid key/IV size for AES-256-GCM\n");
return 1;
}
// Create socket
const int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1)
{
perror("Failed to create socket");
return 1;
}
// Bind to the set port and IP
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
perror("Failed to bind socket");
close(server_socket);
return 1;
}
printf("Done with binding with IP: %s, Port: %d\n", server_ip, server_port);
// Listen for clients:
const char *client_ip = getenv("CLIENT_IP");
if (listen(server_socket, 1) == -1)
{
perror("Failed to listen on socket");
close(server_socket);
return 1;
}
printf("Listening for incoming connections...\n");
// Accept an incoming connection
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_socket == -1)
{
perror("Failed to accept connection");
close(server_socket);
return 1;
}
printf("Client connected at IP: %s \n", client_ip);
// clean exising buffer
memset(client_message, 0, sizeof(client_message));
// store message history, for TAG calculation
// msg on odd is msg, and on even is corresponding TAG
vector<string> message_history;
// Receive client's message
while (1)
{
// clean exising buffer
memset(client_message, 0, sizeof(client_message));
ssize_t n = recv(client_socket, client_message, sizeof(client_message), 0);
if (n == 0)
break;
if (n < 0)
{
perror("recv");
break;
}
if (strcmp((char *)client_message, "\\exit\n") == 0)
break;
printf("Msg from client: %s \n", byte_to_hex((unsigned char *)client_message, n));
// store message history for TAG calculation
message_history.push_back(string((char *)client_message, n));
// only respond after receiving both msg and TAG
if (message_history.size() % 2 == 1)
continue;
// check TAG
const string tag_str = message_history.back();
unsigned char tag[16];
memcpy(tag, tag_str.c_str(), 16);
const string ciphertext_str = message_history[message_history.size() - 2];
char ciphertext_cstr[2048];
strcpy(ciphertext_cstr, ciphertext_str.c_str());
unsigned char plaintext[2048];
memset(plaintext, 0, sizeof(plaintext));
int plaintext_len = gcm_decrypt((unsigned char *)ciphertext_cstr,
strlen(ciphertext_cstr),
(unsigned char *)add,
strlen(add),
tag,
key_bytes,
iv_bytes,
16,
plaintext);
if (plaintext_len == 0)
{
fprintf(stderr, "TAG mismatch or invalid message\n");
memcpy(server_message, custom_message_failed, strlen(custom_message_failed));
;
}
else
{
fprintf(stderr, "Decryption success\n, decrypted message: %s\n", plaintext);
memcpy(server_message, custom_message_success, strlen(custom_message_success));
;
}
// Respond to client
// prepare server message
size_t reply_len = strlen(server_message);
if (send(client_socket, server_message, reply_len, 0) == -1)
{
perror("Send failed");
break;
}
printf("Response sent to client: %s\n", server_message);
}
// Close the socket
close(client_socket);
close(server_socket);
return 0;
}
\end{lstlisting}
\item Quick overview of what the code does in your own words.
We use \texttt{aes-256-gcm} to decrypt the message, with period of 2, we load cipher text and TAG from the message history received from the client.
Then we use \texttt{gcm\_decrypt} to decrypt the message, and check the TAG on the process of decryption.
\item Screenshots showing your server program during/after execution.
Here is the screenshot from the server side receiving the message from the client.
\begin{figure}[H]
\centering
\includegraphics[width=0.9\textwidth]{./images/server.png}
\end{figure}
\end{enumerate}
\newpage
\item[3.] Client code
\begin{enumerate}
\item Screenshot or well-formatted copy of code
Here is the client code used for this assignment.
\begin{lstlisting}[language=c++]
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// open ssl
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <errno.h> // error handling
#include <unistd.h> // for close()
#include <string>
#include <cstring>
#include <cstdlib> // for atoi()
#include <vector>
#include "helper.h"
// GCM encryption
int gcm_encrypt(unsigned char *plaintext, int plaintext_len,
unsigned char *aad, int aad_len,
unsigned char *key,
unsigned char *iv, int iv_len,
unsigned char *ciphertext,
unsigned char *tag)
{
EVP_CIPHER_CTX *ctx;
int len, ciphertext_len;
/* Create and initialize the context */
ctx = EVP_CIPHER_CTX_new();
if (!ctx)
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Initialize the encryption operation. */
if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Set IV length if default 12 bytes (96 bits) is not appropriate
*/
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
};
/* Initialize key and IV */
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Provide any AAD data. This can be called zero or more times as
* required
*/
if (!EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Provide the message to be encrypted, and obtain the encrypted
output.
* EVP_EncryptUpdate can be called multiple times if necessary
*/
if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
ciphertext_len = len;
/*
* Finalize the encryption. Normally ciphertext bytes may be written at
* this stage, but this does not occur in GCM mode
*/
if (!EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
ciphertext_len += len;
/* Get the tag */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
int main(void)
{
load_env(".env");
// Declare variables
const char *server_ip = std::getenv("SERVER_IP");
const int server_port = std::atoi(std::getenv("SERVER_PORT"));
printf("Connecting to server %s:%d\n", server_ip, server_port);
char client_message[1024];
char server_message[1024];
const char *custom_message = "Zheyuan Wu: ";
// encryption parameters
const char *inital_vector = std::getenv("INITIAL_VECTOR");
const char *secret_key = std::getenv("SECRET_KEY");
const char *add = std::getenv("ADD");
unsigned char key_bytes[32], iv_bytes[16];
int key_len = hex_to_bytes_upper(secret_key, key_bytes, sizeof(key_bytes));
int iv_len = hex_to_bytes_upper(inital_vector, iv_bytes, sizeof(iv_bytes));
if (key_len != 32 || iv_len != 16)
{
fprintf(stderr, "Invalid key/IV size for AES-256-GCM\n");
return 1;
}
// Create socket:
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1)
{
perror("Failed to create socket");
return 1;
}
else
{
printf("Socket created successfully\n");
}
// Send connection request to server, be sure to set por tand IP the same as server-side
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
perror("Failed to connect to server");
close(client_socket);
return 1;
}
else
{
printf("Connected to server successfully\n");
}
// Get input from the user:
printf("Enter message sent to the server (type \\quit to exit): ");
// clean the buffer
memset(client_message, 0, sizeof(client_message));
if (fgets(client_message, sizeof(client_message), stdin) == NULL)
{
// EOF or error reading from stdin, exit the loop
perror("Error reading from stdin");
return 1;
}
while (strcmp(client_message, "\\quit\n") != 0)
{
// Send the message to server:
// add my name in the front
char buffer[2048];
std::snprintf(buffer, sizeof(buffer), "%s%s", custom_message, client_message);
printf("Message sent to server: %s, length: %d \n", buffer, (int)strlen(buffer));
unsigned char tag[16] = {0};
unsigned char cipher_text[2048] = {0};
int cipher_text_len = gcm_encrypt((unsigned char *)buffer,
(int)strlen(buffer),
(unsigned char *)add,
(int)strlen(add),
key_bytes,
iv_bytes,
iv_len,
cipher_text,
tag);
printf("Ciphertext sent to server: %s\n", byte_to_hex(cipher_text, cipher_text_len));
ssize_t sent = send(client_socket, cipher_text, cipher_text_len, 0);
if (sent <= 0)
{
perror("No message sent to server");
break;
}
sleep(1);
// send tag of the message
printf("TAG sent to server: %s\n", byte_to_hex(tag, 16));
ssize_t sent_tag = send(client_socket, tag, 16, 0);
if (sent_tag <= 0)
{
perror("No TAG sent to server");
break;
}
// Receive the server's response:
// add terminator for string
ssize_t recvd = recv(client_socket, server_message, sizeof(server_message) - 1, 0);
if (recvd <= 0)
{
perror("No message received from server");
break;
}
server_message[recvd] = '\0';
printf("Server's response: %s\n", server_message);
printf("Enter message sent to the server (type \\quit to exit): ");
// clean the buffer
memset(client_message, 0, sizeof(client_message));
memset(server_message, 0, sizeof(server_message));
if (fgets(client_message, sizeof(client_message), stdin) == NULL)
{
// EOF or error reading from stdin, exit the loop
perror("Error reading from stdin");
break;
}
}
// Close the socket
close(client_socket);
return 0;
}
\end{lstlisting}
\item Quick overview of what the code does in your own words.
We use \texttt{aes-256-gcm} for encryption, we read the add, initial vector, and key from environment variable. First we encrypt the add, then we encrypt the message, and finally we send the ciphertext to the server.
\item Screenshots showing your client program during/after execution
Here is the screenshot from the client side sending message to the server and server response with plain text.
\begin{figure}[H]
\centering
\includegraphics[width=0.9\textwidth]{./images/client.png}
\end{figure}
\end{enumerate}
\newpage
\newpage
\end{enumerate}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Actual math ends here. Don't put any content below the \end{document} line.
%%
\end{document}

BIN
latex/images/client.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

BIN
latex/images/server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

1
server-log.sh Normal file
View File

@@ -0,0 +1 @@
docker logs hw4-server --tail=50

271
server.cpp Normal file
View File

@@ -0,0 +1,271 @@
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// open ssl
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <errno.h> // for perror()
#include <unistd.h> // for close()
#include <string>
#include <cstring>
#include <cstdlib> // for atoi()
#include <vector>
// load environment variables from .env file, and additional helper functions
#include "helper.h"
using std::string;
using std::vector;
int gcm_decrypt(unsigned char *ciphertext, int ciphertext_len,
unsigned char *aad, int aad_len,
unsigned char *tag,
unsigned char *key,
unsigned char *iv, int iv_len,
unsigned char *plaintext)
{
EVP_CIPHER_CTX *ctx;
int len, plaintext_len, ret;
/* Create and initialize the context */
ctx = EVP_CIPHER_CTX_new();
if (!ctx)
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Initialize the decryption operation. */
if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/* Initialize key and IV */
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Provide any AAD data. This can be called zero or more times as
* required
*/
if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Provide the message to be decrypted, and obtain the plaintext
output.
* EVP_DecryptUpdate can be called multiple times if necessary
*/
if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
plaintext_len = len;
/* Set expected tag value. Works in OpenSSL 1.0.1d and later */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag))
{
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
/*
* Finalize the decryption. A positive return value indicates success,
* and anything else is a failure - the plaintext is not trustworthy.
*/
ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
if (ret > 0)
{
/* Success */
plaintext_len += len;
return plaintext_len;
}
else
{
/* Verify failed */
return 0;
}
}
int main(void)
{
printf("Server starting...\n");
load_env(".env");
// Declare variables
const char *server_ip = getenv("SERVER_IP");
const int server_port = atoi(getenv("SERVER_PORT"));
char client_message[2048];
char server_message[2048];
const char *custom_message_success = "Server: Hello from server, message authenticated!\n";
const char *custom_message_failed = "Server: Hello from server, message failed authentication!\n";
// debug
printf("Server starting at IP: %s, Port: %d\n", server_ip, server_port);
// encryption parameters
const char *inital_vector = std::getenv("INITIAL_VECTOR");
const char *secret_key = std::getenv("SECRET_KEY");
const char *add = std::getenv("ADD");
unsigned char key_bytes[32], iv_bytes[16];
int key_len = hex_to_bytes_upper(secret_key, key_bytes, sizeof(key_bytes));
int iv_len = hex_to_bytes_upper(inital_vector, iv_bytes, sizeof(iv_bytes));
if (key_len != 32 || iv_len != 16)
{
fprintf(stderr, "Invalid key/IV size for AES-256-GCM\n");
return 1;
}
// Create socket
const int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1)
{
perror("Failed to create socket");
return 1;
}
// Bind to the set port and IP
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
perror("Failed to bind socket");
close(server_socket);
return 1;
}
printf("Done with binding with IP: %s, Port: %d\n", server_ip, server_port);
// Listen for clients:
const char *client_ip = getenv("CLIENT_IP");
if (listen(server_socket, 1) == -1)
{
perror("Failed to listen on socket");
close(server_socket);
return 1;
}
printf("Listening for incoming connections...\n");
// Accept an incoming connection
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_socket == -1)
{
perror("Failed to accept connection");
close(server_socket);
return 1;
}
printf("Client connected at IP: %s \n", client_ip);
// clean exising buffer
memset(client_message, 0, sizeof(client_message));
// store message history, for TAG calculation
// msg on odd is msg, and on even is corresponding TAG
vector<string> message_history;
// Receive client's message
while (1)
{
// clean exising buffer
memset(client_message, 0, sizeof(client_message));
ssize_t n = recv(client_socket, client_message, sizeof(client_message), 0);
if (n == 0)
break;
if (n < 0)
{
perror("recv");
break;
}
if (strcmp((char *)client_message, "\\exit\n") == 0)
break;
printf("Msg from client: %s \n", byte_to_hex((unsigned char *)client_message, n));
// store message history for TAG calculation
message_history.push_back(string((char *)client_message, n));
// only respond after receiving both msg and TAG
if (message_history.size() % 2 == 1)
continue;
// check TAG
const string tag_str = message_history.back();
unsigned char tag[16];
memcpy(tag, tag_str.c_str(), 16);
const string ciphertext_str = message_history[message_history.size() - 2];
char ciphertext_cstr[2048];
strcpy(ciphertext_cstr, ciphertext_str.c_str());
unsigned char plaintext[2048];
memset(plaintext, 0, sizeof(plaintext));
int plaintext_len = gcm_decrypt((unsigned char *)ciphertext_cstr,
strlen(ciphertext_cstr),
(unsigned char *)add,
strlen(add),
tag,
key_bytes,
iv_bytes,
16,
plaintext);
if (plaintext_len == 0)
{
fprintf(stderr, "TAG mismatch or invalid message\n");
memcpy(server_message, custom_message_failed, strlen(custom_message_failed));
;
}
else
{
fprintf(stderr, "Decryption success\n, decrypted message: %s\n", plaintext);
memcpy(server_message, custom_message_success, strlen(custom_message_success));
;
}
// Respond to client
// prepare server message
size_t reply_len = strlen(server_message);
if (send(client_socket, server_message, reply_len, 0) == -1)
{
perror("Send failed");
break;
}
printf("Response sent to client: %s\n", server_message);
}
// Close the socket
close(client_socket);
close(server_socket);
return 0;
}