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

Cryptography in C++

C++ cryptography — OpenSSL hashing, encryption/decryption, random bytes, HMAC, TLS with Asio, and libsodium for modern crypto.

TL;DR

Use libsodium for new code — simple, hard to misuse. Use OpenSSL when you need protocol-level TLS control. Never implement your own crypto. Always use secure random for keys and nonces.


Hashing with OpenSSL (EVP API)

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

std::vector<uint8_t> sha256(std::string_view data) {
    unsigned int len = 0;
    std::vector<uint8_t> hash(EVP_MAX_MD_SIZE);

    EVP_MD_CTX* ctx = EVP_MD_CTX_new();
    EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
    EVP_DigestUpdate(ctx, data.data(), data.size());
    EVP_DigestFinal_ex(ctx, hash.data(), &len);
    EVP_MD_CTX_free(ctx);

    hash.resize(len);  // 32 bytes for SHA-256
    return hash;
}

// Hex encoding
std::string to_hex(std::span<const uint8_t> bytes) {
    std::string hex;
    hex.reserve(bytes.size() * 2);
    for (uint8_t b : bytes)
        std::format_to(std::back_inserter(hex), "{:02x}", b);
    return hex;
}

// Usage
auto hash = sha256("hello world");
std::println("SHA-256: {}", to_hex(hash));
// SHA-256: b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576b7...

HMAC-SHA256

For message authentication (verify data was not tampered with):

cpp
#include <openssl/hmac.h>

std::vector<uint8_t> hmac_sha256(std::string_view key, std::string_view data) {
    unsigned int len = 0;
    std::vector<uint8_t> result(EVP_MAX_MD_SIZE);

    HMAC(EVP_sha256(),
         key.data(), static_cast<int>(key.size()),
         reinterpret_cast<const uint8_t*>(data.data()), data.size(),
         result.data(), &len);

    result.resize(len);
    return result;
}

// Constant-time comparison (prevent timing attacks)
bool secure_equals(std::span<const uint8_t> a, std::span<const uint8_t> b) {
    if (a.size() != b.size()) return false;
    return CRYPTO_memcmp(a.data(), b.data(), a.size()) == 0;
}

// Verify
auto expected_mac = hmac_sha256("secret-key", "data");
auto received_mac = /* from request */;
if (secure_equals(expected_mac, received_mac))
    std::println("valid");

Symmetric Encryption: AES-256-GCM

Authenticated encryption — provides confidentiality + integrity:

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

struct AesGcmCiphertext {
    std::vector<uint8_t> nonce;    // 12 bytes
    std::vector<uint8_t> tag;      // 16 bytes
    std::vector<uint8_t> ciphertext;
};

AesGcmCiphertext aes256gcm_encrypt(
    std::span<const uint8_t> key,    // must be 32 bytes
    std::span<const uint8_t> plaintext)
{
    AesGcmCiphertext result;
    result.nonce.resize(12);
    RAND_bytes(result.nonce.data(), 12);  // random nonce — never reuse!

    result.ciphertext.resize(plaintext.size());
    result.tag.resize(16);

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    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.data(), result.nonce.data());

    int out_len;
    EVP_EncryptUpdate(ctx, result.ciphertext.data(), &out_len,
                      plaintext.data(), plaintext.size());

    EVP_EncryptFinal_ex(ctx, result.ciphertext.data() + out_len, &out_len);
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, result.tag.data());
    EVP_CIPHER_CTX_free(ctx);

    return result;
}

Secure Random Numbers

cpp
#include <openssl/rand.h>

// Cryptographically secure random bytes
std::vector<uint8_t> random_bytes(size_t n) {
    std::vector<uint8_t> buf(n);
    if (RAND_bytes(buf.data(), static_cast<int>(n)) != 1)
        throw std::runtime_error("RAND_bytes failed");
    return buf;
}

// Generate a 256-bit key
auto key = random_bytes(32);

// Generate a UUID v4 (using OpenSSL random)
std::string uuid_v4() {
    auto bytes = random_bytes(16);
    bytes[6] = (bytes[6] & 0x0F) | 0x40;  // version 4
    bytes[8] = (bytes[8] & 0x3F) | 0x80;  // variant RFC 4122
    return std::format(
        "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-"
        "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
        bytes[0], bytes[1], bytes[2], bytes[3],
        bytes[4], bytes[5], bytes[6], bytes[7],
        bytes[8], bytes[9], bytes[10], bytes[11],
        bytes[12], bytes[13], bytes[14], bytes[15]);
}

libsodium — Modern Simple API

libsodium is easier to use correctly than OpenSSL:

cpp
#include <sodium.h>

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

// Symmetric encryption: XSalsa20-Poly1305 (authenticated)
void secret_box_example() {
    // Generate key
    uint8_t key[crypto_secretbox_KEYBYTES];
    crypto_secretbox_keygen(key);

    // Generate nonce (random, unique per message)
    uint8_t nonce[crypto_secretbox_NONCEBYTES];
    randombytes_buf(nonce, sizeof(nonce));

    std::string message = "secret message";
    std::vector<uint8_t> ciphertext(crypto_secretbox_MACBYTES + message.size());

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

    // Decrypt + verify
    std::vector<uint8_t> plaintext(message.size());
    if (crypto_secretbox_open_easy(plaintext.data(), ciphertext.data(),
                                   ciphertext.size(), nonce, key) != 0)
        throw std::runtime_error("decryption failed or tampered");
}

// Password hashing (Argon2id)
void password_hash_example() {
    char hash[crypto_pwhash_STRBYTES];
    std::string password = "user-password";

    if (crypto_pwhash_str(hash, password.data(), password.size(),
                          crypto_pwhash_OPSLIMIT_INTERACTIVE,
                          crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0)
        throw std::runtime_error("OOM");

    // Verify
    bool valid = crypto_pwhash_str_verify(
        hash, password.data(), password.size()) == 0;
}

CMake Setup

cmake
find_package(OpenSSL REQUIRED)
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)

# libsodium (via pkg-config or manual)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBSODIUM REQUIRED libsodium)
target_link_libraries(myapp PRIVATE ${LIBSODIUM_LIBRARIES})
target_include_directories(myapp PRIVATE ${LIBSODIUM_INCLUDE_DIRS})

Checklist

  • Never implement your own crypto primitives
  • Use AES-256-GCM or ChaCha20-Poly1305 for symmetric encryption
  • Use ECDH (X25519) or RSA-OAEP for key exchange
  • Never reuse nonces/IVs with the same key
  • Use constant-time comparison for MAC verification (CRYPTO_memcmp)
  • Hash passwords with Argon2id/bcrypt/scrypt — never SHA-256 alone
  • Generate keys/nonces with RAND_bytes or randombytes_buf