Online security and privacy are commonly perceived from the user perspective. Whether you want to host content anonymously or set up your own VPN, you’re using pre-existing services and software. Developers need to build these for you first, though. Welcome to a C tutorial on using ephemeral keys to enhance the security of your client/server connections! If you have ever used plain C sockets in your programs, you know that they are relatively simple to set up and easy to use but expose all transmitted information in the plain. Tools like Wireshark allow anyone with access to the same physical network to potentially read every byte you send or receive this way. In this article, I will show you how to use ephemeral keys in C on Linux to upgrade your connection security and ensure forward secrecy!
You can find the complete source code for this tutorial, including instructions, in this GitHub repository!
Choosing and Installing a Secure Sockets Library
While you could implement encryption algorithms yourself (which is an interesting learning experience), you should rely on established, well-tested, and well-maintained crypto libraries. The reason is simple: Mistakes happen, bugs exist, and many of them lead to broken encryption. In the best case, this leads to a wrong sense of safety; in the worst case, this can cost real money and reputation. In this section, we will lay the foundation for using ephemeral keys in our own C programs.
There are multiple libraries available to choose from. For the purpose of this tutorial, we will use OpenSSL, which enjoys widespread use and popularity, is stable, and supports modern encryption algorithms. For this purpose, and assuming you’re using any Debian derivative (for example, Ubuntu), install the library and development headers for OpenSSL:
sudo apt-get install libssl-dev
This should only take a moment. From now on, the OpenSSL headers and binaries are available for you to develop with. Next, we need to generate SSL certificates and configure ephemeral key exchange.
Introduction to Ephemeral Keys
Ephemeral keys are temporary keys used for a single session, providing forward secrecy. This means that even if an attacker manages to obtain a session’s private key, they cannot decrypt past sessions. This is crucial for protecting sensitive data over time.
Ephemeral key exchange protocols, such as Ephemeral Diffie-Hellman (DHE) and Ephemeral Elliptic Curve Diffie-Hellman (ECDHE), generate unique keys for each session, ensuring that past communications remain secure even if future keys are compromised.
Generating Ephemeral Diffie-Hellman Parameters
First, let’s see how to generate ephemeral Diffie-Hellman (DH) parameters in C using OpenSSL. This involves creating a DH structure and setting the prime and generator values.
#include <openssl/dh.h>
DH* get_dh2048() {
static unsigned char dhp_2048[] = {
// Prime (p)
};
static unsigned char dhg_2048[] = {
// Generator (g)
};
DH* dh = DH_new();
if (dh == NULL) return NULL;
BIGNUM* p = BN_bin2bn(dhp_2048, sizeof(dhp_2048), NULL);
BIGNUM* g = BN_bin2bn(dhg_2048, sizeof(dhg_2048), NULL);
if (p == NULL || g == NULL || !DH_set0_pqg(dh, p, NULL, g)) {
DH_free(dh);
return NULL;
}
return dh;
}
// Example usage in SSL context
SSL_CTX* ctx = SSL_CTX_new(TLS_method());
DH* dh = get_dh2048();
if (dh == NULL) {
// Handle error
}
SSL_CTX_set_tmp_dh(ctx, dh);
DH_free(dh);
In this example, we define a function get_dh2048
that creates and returns a DH structure with 2048-bit parameters. The SSL_CTX_set_tmp_dh
function sets these parameters for the SSL context.
Generating Ephemeral Elliptic Curve Diffie-Hellman Keys
Next, we’ll generate ephemeral Elliptic Curve Diffie-Hellman (ECDH) keys. ECDH is preferred over traditional DH due to its stronger security and performance characteristics.
#include <openssl/ec.h>
EC_KEY* get_ecdh_key() {
EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (ecdh == NULL || !EC_KEY_generate_key(ecdh)) {
EC_KEY_free(ecdh);
return NULL;
}
return ecdh;
}
// Example usage in SSL context
SSL_CTX* ctx = SSL_CTX_new(TLS_method());
EC_KEY* ecdh = get_ecdh_key();
if (ecdh == NULL) {
// Handle error
}
SSL_CTX_set_tmp_ecdh(ctx, ecdh);
EC_KEY_free(ecdh);
Here, we define get_ecdh_key
to generate a new ECDH key using the prime256v1
curve. The key is then set for the SSL context using SSL_CTX_set_tmp_ecdh
.
Configuring SSL/TLS for Ephemeral Key Exchange
To enable ephemeral key exchange, we need to configure our SSL/TLS context. This involves setting up both DH and ECDH parameters.
// Example SSL/TLS configuration to enable ephemeral key exchange
SSL_CTX* ctx = SSL_CTX_new(TLS_method());
// Configure to use only secure ciphers that provide forward secrecy
SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256");
// Set up ephemeral Diffie-Hellman parameters
DH* dh = get_dh2048();
if (dh == NULL) {
// Handle error
}
SSL_CTX_set_tmp_dh(ctx, dh);
DH_free(dh);
// Set up ephemeral ECDH parameters
EC_KEY* ecdh = get_ecdh_key();
if (ecdh == NULL) {
// Handle error
}
SSL_CTX_set_tmp_ecdh(ctx, ecdh);
EC_KEY_free(ecdh);
In this configuration, we set the cipher list to include only those that support ephemeral key exchange and configure both DH and ECDH parameters.
Security Benefits of Ephemeral Keys
Ephemeral keys provide several significant security benefits, including forward secrecy. By using ephemeral keys, we ensure that even if a key is compromised, past sessions remain secure. This is especially important for long-term confidentiality and protection against future attacks.
Putting it Together: Ephemeral Keys in Client/Server Applications
To make the above concepts useful, we will put them together in two C programs: A server that accepts client connections, and a client that connects to this server. They will use port 4910 to communicate, and exchange very simple text messages. You should be able to adapt these to your own needs, but for the sake of this tutorial they showcase how to use Ephemeral keys in socket communication.
The below source code can be found on GitHub, with additional logging implemented.
Generating Certificates
In my previous articles I explained how to generally establish a secure sockets connection in C on Linux and how to validate a certificate chain. As described in these posts, you need to generate a certificate for your connection to actually be secure. This is a simple task if you have OpenSSL installed:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
Run this command before the below steps. The server and client will use this generated certificate for communication going forward.
Server Code (server.c)
This server code sets up an SSL context with ephemeral Diffie-Hellman and Elliptic Curve Diffie-Hellman parameters. It accepts client connections and exchanges “PING” and “PONG” messages.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/dh.h>
#include <openssl/ec.h>
#define PORT 4910
void init_openssl() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}
void cleanup_openssl() {
EVP_cleanup();
}
SSL_CTX* create_context() {
const SSL_METHOD* method;
SSL_CTX* ctx;
method = TLS_server_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
DH* get_dh2048() {
static unsigned char dhp_2048[] = {
0xC1,0x51,0x58,0x69,0xFB,0xE8,0x6C,0x47,0x2B,0x86,0x61,0x4F,
0x20,0x2E,0xD3,0xFC,0x19,0xEE,0xB8,0xF3,0x35,0x7D,0xBA,0x86,
0x2A,0xC3,0xC8,0x6E,0xF4,0x99,0x75,0x65,0xD3,0x7A,0x9E,0xDF,
0xD4,0x1F,0x88,0xE3,0x17,0xFC,0xA1,0xED,0xA2,0xB6,0x77,0x84,
0xAA,0x08,0xF2,0x97,0x59,0x7A,0xA0,0x03,0x0D,0x3E,0x7E,0x6D,
0x65,0x6A,0xA4,0xEA,0x54,0xA9,0x52,0x5F,0x63,0xB4,0xBC,0x98,
0x4E,0xF6,0xE1,0xA4,0xEE,0x16,0x0A,0xB0,0x01,0xBD,0x9F,0xA1,
0xE8,0x23,0x29,0x56,0x40,0x95,0x13,0xEB,0xCB,0xD5,0xFC,0x76,
0x1A,0x41,0x26,0xCE,0x20,0xEB,0x30,0x10,0x17,0x07,0xE1,0x8C,
0xAC,0x57,0x37,0x8B,0xE8,0x01,0xDE,0xA9,0xEF,0xA4,0xC2,0xA4,
0x6E,0x48,0x25,0x11,0x33,0x11,0xD4,0x52,0x79,0x87,0x9F,0x75,
0x61,0xF7,0x9C,0x7D,0x36,0x41,0xCB,0xEC,0x8F,0xEA,0x4A,0x47,
0x6A,0x36,0x37,0x75,0xB9,0x8E,0xF5,0x5F,0x67,0xCF,0x1F,0xD8,
0xCA,0x70,0x42,0xC7,0xA2,0xED,0x0F,0x7D,0xBE,0x43,0x08,0x28,
0x66,0x3D,0xDD,0x87,0x0D,0x61,0x6E,0xD0,0xE7,0x49,0xD1,0x70,
0xA9,0x4D,0xD5,0xFD,0xED,0xF2,0x6D,0x32,0x17,0x97,0x5B,0x06,
0x60,0x9C,0x5F,0xA3,0x5D,0x34,0x14,0x7E,0x63,0x54,0xE4,0x7E,
0x09,0x8F,0xBB,0x8E,0xA0,0xD0,0x96,0xAC,0x30,0x20,0x39,0x3B,
0x8C,0x92,0x65,0x37,0x0A,0x8F,0xEC,0x72,0x8B,0x61,0x7D,0x62,
0x24,0x54,0xE9,0x1D,0x01,0x68,0x89,0xC4,0x7B,0x3C,0x48,0x62,
0x9B,0x83,0x11,0x3A,0x0B,0x0D,0xEF,0x5A,0xE4,0x7A,0xA0,0x69,
0xF4,0x54,0xB5,0x5B,
};
static unsigned char dhg_2048[] = {
0x02,
};
DH* dh = DH_new();
if (dh == NULL) return NULL;
BIGNUM* p = BN_bin2bn(dhp_2048, sizeof(dhp_2048), NULL);
BIGNUM* g = BN_bin2bn(dhg_2048, sizeof(dhg_2048), NULL);
if (p == NULL || g == NULL || !DH_set0_pqg(dh, p, NULL, g)) {
DH_free(dh);
return NULL;
}
return dh;
}
EC_KEY* get_ecdh_key() {
EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (ecdh == NULL || !EC_KEY_generate_key(ecdh)) {
EC_KEY_free(ecdh);
return NULL;
}
return ecdh;
}
void configure_context(SSL_CTX* ctx) {
SSL_CTX_set_ecdh_auto(ctx, 1);
DH* dh = get_dh2048();
if (dh == NULL) {
perror("Unable to create DH parameters");
exit(EXIT_FAILURE);
}
SSL_CTX_set_tmp_dh(ctx, dh);
DH_free(dh);
EC_KEY* ecdh = get_ecdh_key();
if (ecdh == NULL) {
perror("Unable to create ECDH key");
exit(EXIT_FAILURE);
}
SSL_CTX_set_tmp_ecdh(ctx, ecdh);
EC_KEY_free(ecdh);
// Load the server certificate and key
if (SSL_CTX_use_certificate_file(ctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (SSL_CTX_use_PrivateKey_file(ctx, "key.pem", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// Set SSL options
SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
// Set a more comprehensive cipher list
if (!SSL_CTX_set_cipher_list(ctx, "DEFAULT:!aNULL:!MD5:!RC4")) {
perror("Unable to set cipher list");
exit(EXIT_FAILURE);
}
}
int main() {
int server_sock;
struct sockaddr_in addr;
SSL_CTX* ctx;
init_openssl();
ctx = create_context();
configure_context(ctx);
server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Unable to bind");
exit(EXIT_FAILURE);
}
if (listen(server_sock, 1) < 0) {
perror("Unable to listen");
exit(EXIT_FAILURE);
}
while (1) {
struct sockaddr_in client_addr;
uint32_t len = sizeof(client_addr);
SSL* ssl;
const char reply[] = "PONG";
int client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &len);
if (client_sock < 0) {
perror("Unable to accept");
exit(EXIT_FAILURE);
}
ssl = SSL_new(ctx);
SSL_set_fd(ssl, client_sock);
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
char buf[256] = {0};
SSL_read(ssl, buf, sizeof(buf));
printf("Received: %s\n", buf);
SSL_write(ssl, reply, strlen(reply));
}
SSL_free(ssl);
close(client_sock);
}
close(server_sock);
SSL_CTX_free(ctx);
cleanup_openssl();
return 0;
}
Client Code (client.c)
This client code sets up an SSL context, connects to the server, sends a “PING” message, and waits for the “PONG” response.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define PORT 4910
void init_openssl() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}
void cleanup_openssl() {
EVP_cleanup();
}
SSL_CTX* create_context() {
const SSL_METHOD* method;
SSL_CTX* ctx;
method = TLS_client_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// Load the CA certificate
if (!SSL_CTX_load_verify_locations(ctx, "cert.pem", NULL)) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// Set SSL options
SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
// Set a more comprehensive cipher list
if (!SSL_CTX_set_cipher_list(ctx, "DEFAULT:!aNULL:!MD5:!RC4")) {
perror("Unable to set cipher list");
exit(EXIT_FAILURE);
}
return ctx;
}
int main() {
int sock;
struct sockaddr_in addr;
SSL_CTX* ctx;
SSL* ssl;
const char* hostname = "127.0.0.1";
const char* message = "PING";
char buf[256] = {0};
init_openssl();
ctx = create_context();
ssl = SSL_new(ctx);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, hostname, &addr.sin_addr) <= 0) {
perror("Invalid address");
exit(EXIT_FAILURE);
}
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Unable to connect");
exit(EXIT_FAILURE);
}
SSL_set_fd(ssl, sock);
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
SSL_write(ssl, message, strlen(message));
SSL_read(ssl, buf, sizeof(buf));
printf("Received: %s\n", buf);
}
SSL_free(ssl);
close(sock);
SSL_CTX_free(ctx);
cleanup_openssl();
return 0;
}
Compilation
To compile the server and client programs, use the following commands:
gcc -o server server.c -lssl -lcrypto
gcc -o client client.c -lssl -lcrypto
Running the Programs
First, start the server in one terminal:
./server
Then, run the client in another terminal:
./client
The client will connect to the server, send a “PING” message, receive a “PONG” response, and then quit. The server will continue running, ready to serve new clients.
This simple example demonstrates how to set up ephemeral key exchange in a C application using OpenSSL. For a more secure and robust implementation, you should add proper error handling, logging, and resource management as needed.
Troubleshooting SSL Connections
If you believe the connection between your server and client applications doesn’t work right, specifically regarding the handshake process, you can use the OpenSSL command line utility to verify it. Run the server in one terminal, and run this command in another:
openssl s_client -connect 127.0.0.1:4910
This should give you insights about which steps of the handshake process work and which don’t. Expect output similar to the below:
CONNECTED(00000003)
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 293 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
Practical Considerations
While ephemeral keys offer strong security, they also come with performance considerations. Generating new keys for each session can be computationally expensive. Therefore, it’s essential to balance security and performance based on your application’s requirements.
Conclusion
Congratulations, you’ve learned how to use ephemeral keys in C on Linux! By integrating ephemeral key exchange into your applications, you can significantly enhance their security and protect sensitive data against various attacks. This tutorial provided the foundation for implementing both Diffie-Hellman and Elliptic Curve Diffie-Hellman ephemeral key exchange using OpenSSL. The full source code is available on GitHub for your reference. There it contains additional command line logging, printing additional information about the handshake process.
For further reading, check out my previous articles on How to Use Secure Sockets in C on Linux and How to Implement Chain Validation in C on Linux.
If you likes this article or want to share your own thoughts and experiences, comment below to get the conversation started!