Cryptography em C: libsodium e OpenSSL basics

1. Introdução à Criptografia em C e Bibliotecas Principais

1.1. Conceitos Fundamentais

Criptografia simétrica usa a mesma chave para cifrar e decifrar, sendo ideal para grandes volumes de dados. Criptografia assimétrica emprega um par de chaves (pública e privada) para troca segura de chaves e assinaturas digitais. Hashing gera impressões digitais unidirecionais de dados, usadas para integridade e autenticação.

1.2. OpenSSL: História e Instalação

OpenSSL é a biblioteca criptográfica mais amplamente utilizada em C, presente em servidores web, sistemas operacionais e aplicações críticas. Para instalação no Ubuntu/Debian:

sudo apt-get install libssl-dev

Headers principais:

#include <openssl/evp.h>   // API de envelope para cifras e hashing
#include <openssl/rand.h>  // Geração de números aleatórios seguros
#include <openssl/err.h>   // Tratamento de erros

Compilação:

gcc -o programa programa.c -lssl -lcrypto

1.3. libsodium: Filosofia de Segurança

libsodium foi projetada para ser simples de usar corretamente, minimizando erros comuns de implementação. Oferece APIs modernas e seguras por padrão.

Instalação:

sudo apt-get install libsodium-dev

Header principal:

#include <sodium.h>

Inicialização obrigatória:

if (sodium_init() < 0) {
    // Falha na inicialização - não continue
}

Compilação:

gcc -o programa programa.c -lsodium

2. Geração de Números Aleatórios Seguros

2.1. OpenSSL: RAND_bytes()

#include <openssl/rand.h>

unsigned char key[32]; // Chave AES-256
if (RAND_bytes(key, sizeof(key)) != 1) {
    // Tratar erro - entropia insuficiente
}

Verifique o estado do gerador:

if (RAND_status() != 1) {
    fprintf(stderr, "Gerador não inicializado\n");
}

2.2. libsodium: randombytes_buf()

#include <sodium.h>

unsigned char key[crypto_secretbox_KEYBYTES];
randombytes_buf(key, sizeof(key));

// Número aleatório em intervalo uniforme
uint32_t valor = randombytes_uniform(100); // 0 a 99

2.3. Comparação

libsodium oferece API mais simples e garante inicialização automática. OpenSSL requer verificação explícita de status. Ambos usam fontes de entropia do sistema operacional.

3. Criptografia Simétrica com OpenSSL (EVP API)

3.1. Cifragem AES-256-GCM

#include <openssl/evp.h>

int encrypt_aes_gcm(unsigned char *plaintext, int plaintext_len,
                    unsigned char *key, unsigned char *iv,
                    unsigned char *ciphertext, unsigned char *tag) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    int len, ciphertext_len;

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

    EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len);
    ciphertext_len = len;

    EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
    ciphertext_len += len;

    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}

3.2. Decifragem

int decrypt_aes_gcm(unsigned char *ciphertext, int ciphertext_len,
                    unsigned char *key, unsigned char *iv,
                    unsigned char *tag, unsigned char *plaintext) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    int len, plaintext_len, ret;

    EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL);
    EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv);

    EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len);
    plaintext_len = len;

    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag);
    ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);

    EVP_CIPHER_CTX_free(ctx);

    if (ret > 0) {
        plaintext_len += len;
        return plaintext_len;
    } else {
        return -1; // Autenticação falhou
    }
}

4. Criptografia Simétrica com libsodium

4.1. Cifragem Autenticada com XChaCha20-Poly1305

#include <sodium.h>

int encrypt_xchacha20(unsigned char *plaintext, size_t plaintext_len,
                      unsigned char *key, unsigned char *ciphertext) {
    unsigned char nonce[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES];
    unsigned long long ciphertext_len;

    randombytes_buf(nonce, sizeof(nonce));

    crypto_aead_xchacha20poly1305_ietf_encrypt(
        ciphertext, &ciphertext_len,
        plaintext, plaintext_len,
        NULL, 0,          // Dados associados
        NULL,             // Nonce final
        nonce, key);

    // Armazenar nonce antes do ciphertext
    memcpy(ciphertext, nonce, sizeof(nonce));
    return ciphertext_len + sizeof(nonce);
}

4.2. Fluxo com crypto_secretstream

void encrypt_file(const char *input_file, const char *output_file,
                  const unsigned char *key) {
    crypto_secretstream_xchacha20poly1305_state state;
    unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
    unsigned char buf[4096], out[4096 + crypto_secretstream_xchacha20poly1305_ABYTES];
    FILE *in = fopen(input_file, "rb"), *outf = fopen(output_file, "wb");

    crypto_secretstream_xchacha20poly1305_init_push(&state, header, key);
    fwrite(header, 1, sizeof(header), outf);

    size_t nread;
    while ((nread = fread(buf, 1, sizeof(buf), in)) > 0) {
        unsigned long long out_len;
        crypto_secretstream_xchacha20poly1305_push(&state, out, &out_len,
            buf, nread, NULL, 0,
            feof(in) ? crypto_secretstream_xchacha20poly1305_TAG_FINAL : 0);
        fwrite(out, 1, out_len, outf);
    }

    fclose(in); fclose(outf);
}

4.3. Derivação de Chave de Senha

unsigned char key[crypto_secretbox_KEYBYTES];
unsigned char salt[crypto_pwhash_SALTBYTES];

randombytes_buf(salt, sizeof(salt));

if (crypto_pwhash(key, sizeof(key), "minha_senha_segura", strlen("minha_senha_segura"),
                  salt, crypto_pwhash_OPSLIMIT_MODERATE,
                  crypto_pwhash_MEMLIMIT_MODERATE,
                  crypto_pwhash_ALG_DEFAULT) != 0) {
    // Falha na derivação (memória insuficiente)
}

5. Hashing e Autenticação de Mensagens

5.1. OpenSSL: SHA-256

#include <openssl/evp.h>

void hash_sha256(const unsigned char *data, size_t len, unsigned char *hash) {
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    unsigned int hash_len;

    EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
    EVP_DigestUpdate(ctx, data, len);
    EVP_DigestFinal_ex(ctx, hash, &hash_len);

    EVP_MD_CTX_free(ctx);
}

5.2. libsodium: BLAKE2b e HMAC

// Hashing genérico
unsigned char hash[crypto_generichash_BYTES];
crypto_generichash(hash, sizeof(hash), data, data_len, NULL, 0);

// HMAC (autenticação de mensagem)
unsigned char key[crypto_auth_KEYBYTES];
unsigned char mac[crypto_auth_BYTES];

randombytes_buf(key, sizeof(key));
crypto_auth(mac, message, message_len, key);

// Verificação
if (crypto_auth_verify(mac, message, message_len, key) == 0) {
    // Mensagem autêntica
}

6. Criptografia de Chave Pública

6.1. OpenSSL: RSA

EVP_PKEY *generate_rsa_keypair(void) {
    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
    EVP_PKEY *pkey = NULL;

    EVP_PKEY_keygen_init(ctx);
    EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048);
    EVP_PKEY_keygen(ctx, &pkey);

    EVP_PKEY_CTX_free(ctx);
    return pkey;
}

6.2. libsodium: Curva Elíptica (crypto_box)

unsigned char alice_sk[crypto_box_SECRETKEYBYTES];
unsigned char alice_pk[crypto_box_PUBLICKEYBYTES];
unsigned char bob_sk[crypto_box_SECRETKEYBYTES];
unsigned char bob_pk[crypto_box_PUBLICKEYBYTES];

crypto_box_keypair(alice_pk, alice_sk);
crypto_box_keypair(bob_pk, bob_sk);

// Cifrar mensagem de Alice para Bob
unsigned char nonce[crypto_box_NONCEBYTES];
unsigned char ciphertext[message_len + crypto_box_MACBYTES];

randombytes_buf(nonce, sizeof(nonce));
crypto_box_easy(ciphertext, message, message_len, nonce, bob_pk, alice_sk);

6.3. Troca de Chaves

libsodium crypto_kx():

unsigned char rx[crypto_kx_SESSIONKEYBYTES];
unsigned char tx[crypto_kx_SESSIONKEYBYTES];

crypto_kx_client_session_keys(rx, tx, client_pk, client_sk, server_pk);

7. Tratamento de Erros e Boas Práticas

7.1. Limpeza de Buffers Sensíveis

// libsodium
sodium_memzero(key, sizeof(key));

// OpenSSL
OPENSSL_cleanse(key, sizeof(key));

// C11 padrão
memset_s(key, sizeof(key), 0, sizeof(key));

7.2. Tratamento de Erros no OpenSSL

if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) {
    ERR_print_errors_fp(stderr);
    // Tratar erro
}

7.3. Alocação Segura

// libsodium - memória protegida contra swapping
void *buf = sodium_malloc(size);
sodium_free(buf);

8. Exemplo Integrado: Aplicação Cliente-Servidor

8.1. Servidor OpenSSL (AES-GCM + RSA)

// Servidor gera par RSA, envia chave pública
// Cliente gera chave AES, cifra com RSA e envia
// Comunicação subsequente usa AES-GCM
// (Implementação completa omitida por brevidade)

8.2. Cliente libsodium (crypto_box)

// Cliente obtém chave pública do servidor
// Gera par efêmero, cifra mensagem com crypto_box
// Servidor decifra com chave secreta correspondente
// (Implementação completa omitida por brevidade)

8.3. Comparação

libsodium oferece APIs mais concisas e seguras por padrão (nonces automáticos, autenticação embutida). OpenSSL requer mais linhas de código e cuidado manual com parâmetros, mas oferece maior flexibilidade e compatibilidade com protocolos existentes.

Referências