Skip to content
C++
Domain Track
Difficulty 3/5

Security and Cryptography in C++

C++ cryptography with OpenSSL (EVP API), libsodium, secure memory handling, constant-time comparisons, TLS with Asio SSL, and common security pitfalls.

TL;DR

Never roll your own crypto. Use OpenSSL's EVP API for standard algorithms or libsodium for a safer, higher-level interface. Always check return codes, zero sensitive buffers, and use constant-time comparison for secrets.

cpp
// libsodium symmetric encryption
unsigned char key[crypto_secretbox_KEYBYTES];
unsigned char nonce[crypto_secretbox_NONCEBYTES];
randombytes_buf(key, sizeof key);
randombytes_buf(nonce, sizeof nonce);

std::vector<unsigned char> ciphertext(msg.size() + crypto_secretbox_MACBYTES);
crypto_secretbox_easy(ciphertext.data(), msg.data(), msg.size(), nonce, key);

OpenSSL EVP: AES-256-GCM Encryption

cpp
#include <openssl/evp.h>
#include <vector>
#include <stdexcept>

// Returns {ciphertext, tag}
std::pair<std::vector<unsigned char>, std::array<unsigned char,16>>
aes_gcm_encrypt(const unsigned char* key,      // 32 bytes
                const unsigned char* iv,       // 12 bytes recommended
                const unsigned char* plaintext, size_t pt_len,
                const unsigned char* aad,      // additional authenticated data
                size_t aad_len) {

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) throw std::runtime_error("EVP_CIPHER_CTX_new failed");

    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr);
    EVP_EncryptInit_ex(ctx, nullptr, nullptr, key, iv);

    int len = 0;
    if (aad && aad_len > 0)
        EVP_EncryptUpdate(ctx, nullptr, &len, aad, (int)aad_len);

    std::vector<unsigned char> ciphertext(pt_len);
    EVP_EncryptUpdate(ctx, ciphertext.data(), &len, plaintext, (int)pt_len);
    int ct_len = len;

    EVP_EncryptFinal_ex(ctx, ciphertext.data() + ct_len, &len);

    std::array<unsigned char, 16> tag;
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag.data());
    EVP_CIPHER_CTX_free(ctx);

    return {ciphertext, tag};
}

OpenSSL EVP: SHA-256 Hash

cpp
#include <openssl/evp.h>
#include <array>

std::array<unsigned char, 32> sha256(const unsigned char* data, size_t len) {
    EVP_MD_CTX* ctx = EVP_MD_CTX_new();
    EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
    EVP_DigestUpdate(ctx, data, len);

    std::array<unsigned char, 32> digest;
    unsigned int digest_len = 32;
    EVP_DigestFinal_ex(ctx, digest.data(), &digest_len);
    EVP_MD_CTX_free(ctx);
    return digest;
}

// Hex representation
std::string to_hex(std::span<const unsigned char> bytes) {
    std::string result;
    result.reserve(bytes.size() * 2);
    for (auto b : bytes)
        result += std::format("{:02x}", b);
    return result;
}

OpenSSL EVP: HMAC-SHA256

cpp
#include <openssl/hmac.h>

std::array<unsigned char, 32> hmac_sha256(
        const unsigned char* key, size_t key_len,
        const unsigned char* data, size_t data_len) {

    std::array<unsigned char, 32> result;
    unsigned int result_len = 32;

    HMAC(EVP_sha256(), key, (int)key_len,
         data, data_len,
         result.data(), &result_len);
    return result;
}

libsodium: Symmetric Encryption

cpp
#include <sodium.h>

// Initialize once at startup
if (sodium_init() < 0) throw std::runtime_error("libsodium init failed");

// Encrypt
void encrypt_message(const std::string& message) {
    unsigned char key[crypto_secretbox_KEYBYTES];   // 32 bytes
    unsigned char nonce[crypto_secretbox_NONCEBYTES]; // 24 bytes

    crypto_secretbox_keygen(key);
    randombytes_buf(nonce, sizeof nonce);

    std::vector<unsigned char> ciphertext(
        message.size() + crypto_secretbox_MACBYTES);

    crypto_secretbox_easy(
        ciphertext.data(),
        reinterpret_cast<const unsigned char*>(message.data()),
        message.size(), nonce, key);
}

// Decrypt
bool decrypt_message(
        const unsigned char* ciphertext, size_t ct_len,
        const unsigned char* nonce, const unsigned char* key,
        std::string& out) {

    std::vector<unsigned char> plaintext(ct_len - crypto_secretbox_MACBYTES);
    if (crypto_secretbox_open_easy(
            plaintext.data(), ciphertext, ct_len, nonce, key) != 0)
        return false;  // authentication failed — tampered or wrong key

    out.assign(plaintext.begin(), plaintext.end());
    return true;
}

libsodium: Ed25519 Signatures

cpp
// Generate a keypair
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
unsigned char sk[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(pk, sk);

// Sign a message
std::string message = "authenticate this";
std::vector<unsigned char> sig(crypto_sign_BYTES);
unsigned long long sig_len;

crypto_sign_detached(
    sig.data(), &sig_len,
    reinterpret_cast<const unsigned char*>(message.data()),
    message.size(), sk);

// Verify
bool ok = crypto_sign_verify_detached(
    sig.data(),
    reinterpret_cast<const unsigned char*>(message.data()),
    message.size(), pk) == 0;

libsodium: Password Hashing (Argon2id)

cpp
char hash[crypto_pwhash_STRBYTES];
const char* password = "hunter2";

// Hash (for storage)
if (crypto_pwhash_str(
        hash, password, strlen(password),
        crypto_pwhash_OPSLIMIT_INTERACTIVE,
        crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0)
    throw std::runtime_error("out of memory for hashing");

// Verify
bool matches = crypto_pwhash_str_verify(
    hash, password, strlen(password)) == 0;

Secure Memory Handling

cpp
// Allocate memory that won't be swapped or appear in core dumps
void* secret = sodium_malloc(32);   // guarded allocation
// or:
unsigned char key[32];
sodium_mlock(key, sizeof key);  // prevent swapping

// ALWAYS zero sensitive data before freeing
sodium_memzero(key, sizeof key);         // libsodium — compiler can't optimize away
OPENSSL_cleanse(key, sizeof key);        // OpenSSL equivalent
// Don't use memset() — optimizer may remove it if buffer is "dead"

// volatile trick (if no library available)
volatile unsigned char* p = key;
for (size_t i = 0; i < sizeof key; ++i) p[i] = 0;

Constant-Time Comparison

cpp
// WRONG: early exit leaks timing info about secret
bool bad_compare(const char* a, const char* b, size_t len) {
    for (size_t i = 0; i < len; ++i)
        if (a[i] != b[i]) return false;  // attacker can measure response time
    return true;
}

// CORRECT: constant-time comparison
bool ct_compare(const unsigned char* a, const unsigned char* b, size_t len) {
    return CRYPTO_memcmp(a, b, len) == 0;     // OpenSSL
    // or: sodium_memcmp(a, b, len) == 0;    // libsodium
}

TLS with Asio SSL

cpp
#include <asio/ssl.hpp>

asio::io_context io;
asio::ssl::context ssl_ctx(asio::ssl::context::tls_client);

// Configure
ssl_ctx.set_verify_mode(asio::ssl::verify_peer);
ssl_ctx.set_default_verify_paths();
ssl_ctx.load_verify_file("ca.crt");

// Create SSL stream
using SSLStream = asio::ssl::stream<asio::ip::tcp::socket>;
SSLStream stream(io, ssl_ctx);

// Set SNI hostname (required for many servers)
SSL_set_tlsext_host_name(stream.native_handle(), "example.com");

// Connect and handshake
asio::ip::tcp::resolver resolver(io);
auto endpoints = resolver.resolve("example.com", "443");
asio::connect(stream.next_layer(), endpoints);
stream.handshake(asio::ssl::stream_base::client);

// Read/write like a normal socket
asio::write(stream, asio::buffer("GET / HTTP/1.0\r\n\r\n"));

std::string response;
asio::error_code ec;
asio::read(stream, asio::dynamic_buffer(response), ec);

Common Pitfalls

cpp
1. Nonce/IV reuse — catastrophic for GCM/CTR modes (breaks confidentiality)
   Fix: use a counter or random nonce, never reuse

2. Not checking return values — OpenSSL returns 1 for success
   Fix: always check EVP_* return codes

3. Weak RNG — using rand() or time-based seeds for crypto
   Fix: use randombytes_buf() (libsodium) or RAND_bytes() (OpenSSL)

4. Key material in core dumps / swap
   Fix: mlock(), guarded allocations, zero on free

5. Timing attacks on secret comparison
   Fix: use constant-time compare (CRYPTO_memcmp, sodium_memcmp)

6. Using ECB mode — reveals plaintext patterns
   Fix: use GCM (authenticated) or CBC with HMAC

7. Short keys — AES-128 is fine, never go below 128 bits

Library Comparison

OpenSSLlibsodiumBotanmbedTLS
API complexityHigh (EVP is verbose)Low (opinionated)MediumMedium
Algorithm choiceUnlimitedCurated safe setWideWide
TLS/DTLSYesNo (use libsodium + hand-roll)YesYes (embedded focus)
FootprintLargeSmallMediumSmall
LicenseApache 2.0ISCSimplified BSDApache 2.0
Best forGeneral use, TLSModern crypto, easy APIWide algorithm supportEmbedded/IoT