Criptografia em Rust: criptografia e hashes com as bibliotecas ring e sha2
Rust é uma linguagem de programação de sistema que garante segurança de memória e alto desempenho. Graças a essas qualidades, ela é ideal para tarefas de criptografia. Neste artigo, veremos duas bibliotecas populares: ring (para criptografia simétrica e hashing) e sha2 (para hashing puro). Você aprenderá como criptografar dados usando AES-GCM, calcular hashes SHA-256 e criar assinaturas HMAC.
Por que Rust para criptografia?
Rust elimina classes inteiras de vulnerabilidades (estouro de buffer, condições de corrida) em tempo de compilação. Bibliotecas criptográficas em Rust, como ring, são escritas em assembly e C com wrappers seguros, proporcionando velocidade comparável ao C, mas com a confiabilidade do Rust.
Configuração do projeto
Crie um novo projeto e adicione as dependências ao Cargo.toml:
[dependencies]ring = "0.17"sha2 = "0.10"hex = "0.4" # para exibição conveniente de bytesCriptografia simétrica: AES-GCM com ring
AES-GCM (Galois/Counter Mode) é um padrão moderno de criptografia simétrica que fornece tanto confidencialidade quanto autenticação (verificação de integridade). A biblioteca ring oferece uma API simples e segura.
Criptografando dados
Para criptografar, você precisa de: uma chave (256 bits), um nonce (96 bits) e o texto simples. O nonce deve ser único para cada criptografia com a mesma chave.
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM};use ring::rand::{SecureRandom, SystemRandom};
fn encrypt_aes_gcm(key: &[u8; 32], plaintext: &[u8]) -> Vec<u8> { let rng = SystemRandom::new(); let unbound_key = UnboundKey::new(&AES_256_GCM, key).unwrap(); let key = LessSafeKey::new(unbound_key);
// Geração de nonce aleatório let mut nonce_bytes = [0u8; 12]; rng.fill(&mut nonce_bytes).unwrap(); let nonce = Nonce::assume_unique_for_key(nonce_bytes);
// Criptografia com dados adicionais vazios (AAD) let mut in_out = plaintext.to_vec(); let tag = key.seal_in_place_separate_tag(nonce, Aad::empty(), &mut in_out).unwrap(); // Retorna nonce + dados criptografados + tag de autenticação let mut result = nonce_bytes.to_vec(); result.extend_from_slice(&in_out); result.extend_from_slice(tag.as_ref()); result}Descriptografando
Para descriptografar, extraímos o nonce, a tag e o texto cifrado, e então chamamos open_in_place.
fn decrypt_aes_gcm(key: &[u8; 32], ciphertext: &[u8]) -> Option<Vec<u8>> { if ciphertext.len() < 28 { return None; } // 12 (nonce) + 16 (tag) mínimo
let (nonce_bytes, rest) = ciphertext.split_at(12); let (encrypted, tag_bytes) = rest.split_at(rest.len() - 16); let nonce = Nonce::assume_unique_for_key(*nonce_bytes); let unbound_key = UnboundKey::new(&AES_256_GCM, key).unwrap(); let key = LessSafeKey::new(unbound_key);
let mut in_out = encrypted.to_vec(); in_out.extend_from_slice(tag_bytes); // ring espera a tag no final match key.open_in_place(nonce, Aad::empty(), &mut in_out) { Ok(plaintext) => { let plain_len = plaintext.len(); Some(plaintext[..plain_len].to_vec()) }, Err(_) => None // chave inválida ou dados corrompidos }}Exemplo de uso
fn main() { let key = b"0123456789abcdef0123456789abcdef"; // 32 bytes let data = b"Mensagem secreta para Rust!"; let encrypted = encrypt_aes_gcm(key, data); println!("Criptografado (hex): {}", hex::encode(&encrypted)); if let Some(decrypted) = decrypt_aes_gcm(key, &encrypted) { println!("Descriptografado: {}", String::from_utf8(decrypted).unwrap())