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
- Documentação Oficial do libsodium — Guia completo com todos os algoritmos e exemplos em C
- OpenSSL EVP API Documentation — Documentação oficial da API de envelope para cifras e hashing
- Practical Cryptography with libsodium — Tutorial prático com exemplos reais de implementação
- OpenSSL Cookbook — Guia prático para uso de OpenSSL em aplicações C
- Cryptography Engineering: Design Principles and Practical Applications — Referência teórica e prática sobre implementação segura em C