Rust

Criptografia e Segurança — Hashing, Criptografia Simétrica e TLS Já leu

19 min de leitura

Criptografia e Segurança — Hashing, Criptografia Simétrica e TLS
Segurança não é uma camada adicional que se adiciona ao software — é uma propriedade que precisa ser construída desde o início. Rust oferece vantagens únicas nesse território: a ausência de buffer overflows, use-after-fr

 

Segurança não é uma camada adicional que se adiciona ao software — é uma propriedade que precisa ser construída desde o início. Rust oferece vantagens únicas nesse território: a ausência de buffer overflows, use-after-free e race conditions elimina classes inteiras de vulnerabilidades comuns em implementações C de criptografia.

Neste artigo cobrimos as operações criptográficas fundamentais que todo desenvolvedor precisa conhecer: hashing criptográfico, HMAC para autenticação de mensagens, criptografia simétrica com AES, geração de números aleatórios seguros, e configuração de TLS para comunicações seguras.

Aviso importante: criptografia é um campo onde erros sutis têm consequências graves. Use sempre bibliotecas auditadas e estabelecidas. Nunca implemente algoritmos criptográficos do zero.


Dependências

[dependencies]
# Criptografia de baixo nível — auditada pela Google
ring = "0.17"

# Hashing: SHA2, SHA3, BLAKE3
sha2 = "0.10"
sha3 = "0.10"
blake3 = "1"

# Hashing de senhas — bcrypt, Argon2, scrypt
argon2 = "0.5"
bcrypt = "0.15"

# Geração de números aleatórios criptograficamente seguros
rand = { version = "0.8", features = ["getrandom"] }
getrandom = "0.2"

# TLS
rustls = "0.22"
rustls-pemfile = "2"
tokio-rustls = "0.25"

# Encoding
hex = "0.4"
base64 = "0.22"

# Constante de tempo (evita timing attacks)
subtle = "2"

Hashing criptográfico

Um hash criptográfico transforma dados de qualquer tamanho em uma saída de tamanho fixo. É determinístico (mesma entrada → mesma saída) e unidirecional (impossível reverter). Use para verificação de integridade, não para senhas:

use sha2::{Digest, Sha256, Sha512};
use sha3::Sha3_256;
use blake3;
use hex;

fn demonstrar_hashing() {
    let dados = b"Rust e seguranca de sistemas";

    // SHA-256 — o mais comum, 256 bits de saída
    let mut hasher = Sha256::new();
    hasher.update(dados);
    let hash_sha256 = hasher.finalize();
    println!("SHA-256:  {}", hex::encode(hash_sha256));

    // SHA-512 — maior resistência, 512 bits
    let hash_sha512 = Sha512::digest(dados);
    println!("SHA-512:  {}...", &hex::encode(hash_sha512)[..32]);

    // SHA3-256 — diferente da família SHA-2, resistente a ataques de extensão
    let hash_sha3 = Sha3_256::digest(dados);
    println!("SHA3-256: {}", hex::encode(hash_sha3));

    // BLAKE3 — extremamente rápido, moderno, paralelizável
    let hash_blake3 = blake3::hash(dados);
    println!("BLAKE3:   {}", hash_blake3.to_hex());

    // Hashing incremental — para dados grandes
    let mut hasher = Sha256::new();
    for chunk in dados.chunks(8) {
        hasher.update(chunk);
    }
    let hash_incremental = hasher.finalize();
    println!("
Hash incremental = hash completo: {}",
        hash_incremental == Sha256::digest(dados));
}

fn verificar_integridade_arquivo(caminho: &str) -> std::io::Result<String> {
    use std::io::Read;
    use std::fs::File;

    let mut arquivo = File::open(caminho)?;
    let mut hasher = blake3::Hasher::new();
    let mut buffer = [0u8; 65536]; // 64KB por vez

    loop {
        let n = arquivo.read(&mut buffer)?;
        if n == 0 { break; }
        hasher.update(&buffer[..n]);
    }

    Ok(hasher.finalize().to_hex().to_string())
}

fn main() {
    demonstrar_hashing();
}

Hashing de senhas — Argon2

Para senhas, nunca use SHA-256 ou MD5 diretamente. Esses são algoritmos rápidos — um atacante com GPU pode testar bilhões de combinações por segundo. Use algoritmos projetados para ser lentos e resistentes a hardware especializado:

use argon2::{
    Argon2,
    password_hash::{
        PasswordHash, PasswordHasher, PasswordVerifier,
        rand_core::OsRng,
        SaltString,
    },
};

fn hash_senha(senha: &str) -> Result<String, argon2::password_hash::Error> {
    // Salt aleatório e único para cada senha
    let salt = SaltString::generate(&mut OsRng);

    // Parâmetros Argon2id — balança segurança e performance
    let argon2 = Argon2::default();

    // Gera o hash no formato PHC (inclui algoritmo, parâmetros e salt)
    let hash = argon2
        .hash_password(senha.as_bytes(), &salt)?
        .to_string();

    Ok(hash)
}

fn verificar_senha(
    senha: &str,
    hash_armazenado: &str,
) -> Result<bool, argon2::password_hash::Error> {
    let hash_parseado = PasswordHash::new(hash_armazenado)?;

    // Extrai os parâmetros do hash armazenado automaticamente
    let resultado = Argon2::default()
        .verify_password(senha.as_bytes(), &hash_parseado);

    Ok(resultado.is_ok())
}

fn demonstrar_argon2() {
    let senhas = ["senha123", "MinhaS3nh@F0rte!", "rust_e_seguro"];

    for senha in &senhas {
        println!("── Senha: {senha} ──");

        let hash = hash_senha(senha).unwrap();
        println!("Hash: {}", &hash[..50]);
        println!("Hash completo tem {} chars", hash.len());

        // Verificação correta
        let correto = verificar_senha(senha, &hash).unwrap();
        println!("Senha correta verificada: {correto}");

        // Verificação incorreta
        let errado = verificar_senha("senha_errada", &hash).unwrap();
        println!("Senha errada verificada: {errado}");

        // Dois hashes da mesma senha são diferentes (salt diferente)
        let hash2 = hash_senha(senha).unwrap();
        println!("Hashes iguais (mesmo salt): {}", hash == hash2);
        println!();
    }
}

fn main() {
    demonstrar_argon2();
}

O formato PHC armazenado parece:

$argon2id$v=19$m=19456,t=2,p=1$[salt_base64]$[hash_base64]

Ele inclui o algoritmo, versão, parâmetros de custo e salt — tudo necessário para verificação futura, sem precisar armazenar separadamente.


HMAC — Autenticação de Mensagens

HMAC (Hash-based Message Authentication Code) verifica que uma mensagem não foi alterada e que veio de alguém que possui a chave secreta:

use hmac::{Hmac, Mac};
use sha2::Sha256;
use hex;

type HmacSha256 = Hmac<Sha256>;

fn gerar_hmac(chave: &[u8], mensagem: &[u8]) -> Vec<u8> {
    let mut mac = HmacSha256::new_from_slice(chave)
        .expect("HMAC aceita chaves de qualquer tamanho");
    mac.update(mensagem);
    mac.finalize().into_bytes().to_vec()
}

fn verificar_hmac(
    chave: &[u8],
    mensagem: &[u8],
    hmac_esperado: &[u8],
) -> bool {
    let mut mac = HmacSha256::new_from_slice(chave)
        .expect("HMAC aceita chaves de qualquer tamanho");
    mac.update(mensagem);

    // verify_slice usa comparação em tempo constante
    // — evita timing attacks
    mac.verify_slice(hmac_esperado).is_ok()
}

fn assinar_payload_api(
    chave_secreta: &str,
    payload: &str,
    timestamp: u64,
) -> String {
    let mensagem = format!("{timestamp}.{payload}");
    let hmac = gerar_hmac(chave_secreta.as_bytes(), mensagem.as_bytes());
    hex::encode(hmac)
}

fn verificar_payload_api(
    chave_secreta: &str,
    payload: &str,
    timestamp: u64,
    assinatura: &str,
    tolerancia_segundos: u64,
) -> Result<(), String> {
    use std::time::{SystemTime, UNIX_EPOCH};

    // Verifica freshness (evita replay attacks)
    let agora = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();

    if agora.saturating_sub(timestamp) > tolerancia_segundos {
        return Err("Timestamp expirado — possível replay attack".to_string());
    }

    // Verifica assinatura
    let mensagem = format!("{timestamp}.{payload}");
    let hmac_esperado = gerar_hmac(
        chave_secreta.as_bytes(),
        mensagem.as_bytes()
    );
    let assinatura_bytes = hex::decode(assinatura)
        .map_err(|_| "Assinatura inválida")?;

    if verificar_hmac(
        chave_secreta.as_bytes(),
        mensagem.as_bytes(),
        &assinatura_bytes,
    ) {
        Ok(())
    } else {
        Err("Assinatura HMAC inválida".to_string())
    }
}

fn main() {
    use std::time::{SystemTime, UNIX_EPOCH};

    let chave = "minha_chave_secreta_longa_e_aleatoria";
    let payload = r#"{"usuario_id": 123, "acao": "deletar"}"#;
    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();

    let assinatura = assinar_payload_api(chave, payload, timestamp);
    println!("Assinatura: {assinatura}");

    match verificar_payload_api(chave, payload, timestamp, &assinatura, 300) {
        Ok(()) => println!("✓ Payload válido e autêntico"),
        Err(e) => println!("✗ Erro: {e}"),
    }

    // Payload adulterado
    let payload_adulterado = r#"{"usuario_id": 999, "acao": "deletar"}"#;
    match verificar_payload_api(chave, payload_adulterado, timestamp, &assinatura, 300) {
        Ok(()) => println!("Não deveria chegar aqui"),
        Err(e) => println!("✓ Adulteração detectada: {e}"),
    }
}

Adicione ao Cargo.toml:

hmac = "0.12"

Criptografia Simétrica com AES-GCM

AES-GCM (Galois/Counter Mode) é a cifra simétrica autenticada mais usada. "Autenticada" significa que detecta automaticamente se o ciphertext foi adulterado — sem precisar de HMAC separado:

use ring::aead::{
    Aad, BoundKey, Nonce, NonceSequence, OpeningKey,
    SealingKey, UnboundKey, AES_256_GCM, NONCE_LEN,
};
use ring::error::Unspecified;
use ring::rand::{SecureRandom, SystemRandom};

struct NonceSingle([u8; NONCE_LEN]);

impl NonceSequence for NonceSingle {
    fn advance(&mut self) -> Result<Nonce, Unspecified> {
        Ok(Nonce::assume_unique_for_key(self.0))
    }
}

pub struct CifradorAes {
    rng: SystemRandom,
}

impl CifradorAes {
    pub fn novo() -> Self {
        CifradorAes { rng: SystemRandom::new() }
    }

    // Gera chave aleatória de 256 bits
    pub fn gerar_chave(&self) -> [u8; 32] {
        let mut chave = [0u8; 32];
        self.rng.fill(&mut chave).unwrap();
        chave
    }

    // Cifra dados — retorna nonce + ciphertext + tag de autenticação
    pub fn cifrar(
        &self,
        chave: &[u8; 32],
        plaintext: &[u8],
        dados_adicionais: &[u8], // AAD: autenticados mas não cifrados
    ) -> Result<Vec<u8>, Unspecified> {
        // Gera nonce aleatório — NUNCA reutilize um nonce com a mesma chave
        let mut nonce_bytes = [0u8; NONCE_LEN];
        self.rng.fill(&mut nonce_bytes)?;

        let unbound = UnboundKey::new(&AES_256_GCM, chave)?;
        let mut sealing = SealingKey::new(unbound, NonceSingle(nonce_bytes));

        let mut buffer = plaintext.to_vec();
        // Reserva espaço para o tag de autenticação (16 bytes)
        buffer.extend_from_slice(&[0u8; 16]);

        sealing.seal_in_place_separate_tag(
            Aad::from(dados_adicionais),
            &mut buffer[..plaintext.len()],
        ).map(|tag| {
            buffer[plaintext.len()..].copy_from_slice(tag.as_ref());
        })?;

        // Resultado: [nonce (12 bytes)] + [ciphertext + tag]
        let mut resultado = nonce_bytes.to_vec();
        resultado.extend_from_slice(&buffer);
        Ok(resultado)
    }

    // Decifra dados — verifica autenticidade automaticamente
    pub fn decifrar(
        &self,
        chave: &[u8; 32],
        ciphertext_com_nonce: &[u8],
        dados_adicionais: &[u8],
    ) -> Result<Vec<u8>, Unspecified> {
        if ciphertext_com_nonce.len() < NONCE_LEN + 16 {
            return Err(Unspecified);
        }

        let (nonce_bytes, ciphertext_e_tag) = ciphertext_com_nonce
            .split_at(NONCE_LEN);

        let mut nonce = [0u8; NONCE_LEN];
        nonce.copy_from_slice(nonce_bytes);

        let unbound = UnboundKey::new(&AES_256_GCM, chave)?;
        let mut opening = OpeningKey::new(unbound, NonceSingle(nonce));

        let mut buffer = ciphertext_e_tag.to_vec();

        let plaintext = opening.open_in_place(
            Aad::from(dados_adicionais),
            &mut buffer,
        )?;

        Ok(plaintext.to_vec())
    }
}

fn demonstrar_aes_gcm() {
    let cifrador = CifradorAes::novo();
    let chave = cifrador.gerar_chave();

    let mensagem = b"Dados confidenciais: numero do cartao 4111-1111-1111-1111";
    let metadados = b"usuario_id:123"; // AAD: autenticado, não cifrado

    println!("Mensagem original: {}", String::from_utf8_lossy(mensagem));
    println!("Tamanho original : {} bytes", mensagem.len());

    // Cifrar
    let ciphertext = cifrador
        .cifrar(&chave, mensagem, metadados)
        .expect("Falha ao cifrar");

    println!("Ciphertext (hex) : {}...", &hex::encode(&ciphertext)[..40]);
    println!("Tamanho cifrado  : {} bytes", ciphertext.len());

    // Decifrar
    let plaintext = cifrador
        .decifrar(&chave, &ciphertext, metadados)
        .expect("Falha ao decifrar");

    println!("Decifrado        : {}", String::from_utf8_lossy(&plaintext));
    println!("Igual ao original: {}", plaintext == mensagem);

    // Adulteração detectada
    let mut ciphertext_adulterado = ciphertext.clone();
    ciphertext_adulterado[20] ^= 0xFF; // flip de bit

    match cifrador.decifrar(&chave, &ciphertext_adulterado, metadados) {
        Ok(_)  => println!("Não deveria decifrar!"),
        Err(_) => println!("✓ Adulteração detectada — decifração recusada"),
    }

    // AAD adulterado também é detectado
    match cifrador.decifrar(&chave, &ciphertext, b"usuario_id:999") {
        Ok(_)  => println!("Não deveria decifrar!"),
        Err(_) => println!("✓ AAD adulterado detectado"),
    }
}

fn main() {
    demonstrar_aes_gcm();
}

Geração de números aleatórios seguros

use rand::{RngCore, rngs::OsRng};
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};

fn gerar_token_seguro(tamanho_bytes: usize) -> String {
    let mut bytes = vec![0u8; tamanho_bytes];
    OsRng.fill_bytes(&mut bytes);
    URL_SAFE_NO_PAD.encode(&bytes)
}

fn gerar_id_sessao() -> String {
    gerar_token_seguro(32) // 256 bits de entropia
}

fn gerar_api_key() -> String {
    // Prefixo para identificar o tipo de chave
    format!("ak_{}", gerar_token_seguro(24))
}

fn gerar_codigo_verificacao() -> String {
    // Código numérico de 6 dígitos para 2FA
    let mut bytes = [0u8; 4];
    OsRng.fill_bytes(&mut bytes);
    let n = u32::from_be_bytes(bytes);
    format!("{:06}", n % 1_000_000)
}

fn gerar_salt_customizado() -> [u8; 32] {
    let mut salt = [0u8; 32];
    OsRng.fill_bytes(&mut salt);
    salt
}

fn main() {
    println!("Token (256 bits): {}", gerar_token_seguro(32));
    println!("ID de sessão    : {}", gerar_id_sessao());
    println!("API Key         : {}", gerar_api_key());
    println!("Código 2FA      : {}", gerar_codigo_verificacao());
    println!("Salt (hex)      : {}", hex::encode(gerar_salt_customizado()));

    // Gerando múltiplos — todos diferentes
    let tokens: Vec<String> = (0..3)
        .map(|_| gerar_token_seguro(16))
        .collect();
    println!("
Tokens únicos:");
    for t in &tokens {
        println!("  {t}");
    }
}

TLS com rustls

rustls é uma implementação de TLS em Rust puro — sem dependência de OpenSSL. É auditada, moderna (TLS 1.2 e 1.3 apenas), e usada em produção por grandes sistemas:

use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::{TlsAcceptor, TlsConnector};
use rustls::{ClientConfig, ServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::sync::Arc;
use std::io::BufReader;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

async fn carregar_config_servidor(
    cert_path: &str,
    key_path: &str,
) -> Result<Arc<ServerConfig>, Box<dyn std::error::Error>> {
    let cert_file = std::fs::File::open(cert_path)?;
    let key_file  = std::fs::File::open(key_path)?;

    let certs_der: Vec<_> = certs(&mut BufReader::new(cert_file))
        .collect::<Result<_, _>>()?;

    let mut keys = pkcs8_private_keys(&mut BufReader::new(key_file))
        .collect::<Result<Vec<_>, _>>()?;

    let config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(
            certs_der,
            rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0))
        )?;

    Ok(Arc::new(config))
}

async fn servidor_tls(
    endereco: &str,
    cert_path: &str,
    key_path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let config = carregar_config_servidor(cert_path, key_path).await?;
    let acceptor = TlsAcceptor::from(config);
    let listener = TcpListener::bind(endereco).await?;

    println!("Servidor TLS em {endereco}");

    loop {
        let (stream, addr) = listener.accept().await?;
        let acceptor = acceptor.clone();

        tokio::spawn(async move {
            match acceptor.accept(stream).await {
                Ok(mut tls_stream) => {
                    println!("[{addr}] TLS handshake bem-sucedido");

                    let mut buf = vec![0u8; 1024];
                    if let Ok(n) = tls_stream.read(&mut buf).await {
                        let msg = String::from_utf8_lossy(&buf[..n]);
                        println!("[{addr}] Recebido: {msg}");

                        let resposta = format!(
                            "HTTP/1.1 200 OK
\
                             Content-Type: text/plain
\
                             
\
                             Olá via TLS de {}!
", addr
                        );
                        let _ = tls_stream.write_all(resposta.as_bytes()).await;
                    }
                }
                Err(e) => eprintln!("[{addr}] Falha no TLS: {e}"),
            }
        });
    }
}

// Para gerar certificados de desenvolvimento:
// openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

Um programa completo: cofre de senhas

Vamos construir um cofre de senhas de linha de comando que combina Argon2 para a senha mestre, AES-GCM para cifrar as entradas, e persistência em arquivo JSON:

use argon2::{
    Argon2,
    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
};
use rand::rngs::OsRng;
use ring::aead::{Aad, BoundKey, NONCE_LEN, NonceSequence, OpeningKey, SealingKey,
                 UnboundKey, AES_256_GCM, Nonce};
use ring::error::Unspecified;
use ring::rand::{SecureRandom, SystemRandom};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use base64::{Engine, engine::general_purpose::STANDARD};

#[derive(Debug, Serialize, Deserialize)]
struct Cofre {
    hash_senha_mestre: String,
    entradas: HashMap<String, EntradaCifrada>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct EntradaCifrada {
    usuario: String,
    dados_cifrados: String, // base64(nonce + ciphertext)
    notas: Option<String>,
}

#[derive(Debug)]
struct EntradaDecifrada {
    usuario: String,
    senha: String,
    notas: Option<String>,
}

struct NonceSingle([u8; NONCE_LEN]);
impl NonceSequence for NonceSingle {
    fn advance(&mut self) -> Result<Nonce, Unspecified> {
        Ok(Nonce::assume_unique_for_key(self.0))
    }
}

fn derivar_chave_de_senha(
    senha: &str,
    salt: &[u8],
) -> [u8; 32] {
    let mut chave = [0u8; 32];
    // Argon2 como KDF — deriva chave da senha
    Argon2::default()
        .hash_password_into(
            senha.as_bytes(),
            salt,
            &mut chave,
        )
        .expect("Falha na derivação de chave");
    chave
}

impl Cofre {
    fn novo(senha_mestre: &str) -> Self {
        let salt = SaltString::generate(&mut OsRng);
        let hash = Argon2::default()
            .hash_password(senha_mestre.as_bytes(), &salt)
            .unwrap()
            .to_string();

        Cofre {
            hash_senha_mestre: hash,
            entradas: HashMap::new(),
        }
    }

    fn verificar_senha_mestre(&self, senha: &str) -> bool {
        let hash = PasswordHash::new(&self.hash_senha_mestre).unwrap();
        Argon2::default()
            .verify_password(senha.as_bytes(), &hash)
            .is_ok()
    }

    fn adicionar(
        &mut self,
        nome: &str,
        usuario: &str,
        senha: &str,
        notas: Option<&str>,
        senha_mestre: &str,
    ) -> Result<(), String> {
        let rng = SystemRandom::new();

        // Deriva chave da senha mestre
        // Usa os primeiros 16 bytes do hash como salt para derivação
        let salt = &self.hash_senha_mestre.as_bytes()[..16.min(
            self.hash_senha_mestre.len()
        )];
        let chave = derivar_chave_de_senha(senha_mestre, salt);

        // Cifra a senha da entrada
        let mut nonce_bytes = [0u8; NONCE_LEN];
        rng.fill(&mut nonce_bytes).map_err(|_| "RNG error")?;

        let unbound = UnboundKey::new(&AES_256_GCM, &chave)
            .map_err(|_| "Chave inválida")?;
        let mut sealing = SealingKey::new(
            unbound, NonceSingle(nonce_bytes)
        );

        let mut buffer = senha.as_bytes().to_vec();
        buffer.extend_from_slice(&[0u8; 16]);

        sealing.seal_in_place_separate_tag(
            Aad::from(nome.as_bytes()),
            &mut buffer[..senha.len()],
        ).map(|tag| {
            buffer[senha.len()..].copy_from_slice(tag.as_ref());
        }).map_err(|_| "Erro ao cifrar")?;

        let mut dados = nonce_bytes.to_vec();
        dados.extend_from_slice(&buffer);

        self.entradas.insert(nome.to_string(), EntradaCifrada {
            usuario: usuario.to_string(),
            dados_cifrados: STANDARD.encode(&dados),
            notas: notas.map(str::to_string),
        });

        Ok(())
    }

    fn obter(
        &self,
        nome: &str,
        senha_mestre: &str,
    ) -> Result<EntradaDecifrada, String> {
        let entrada = self.entradas.get(nome)
            .ok_or_else(|| format!("Entrada '{nome}' não encontrada"))?;

        let salt = &self.hash_senha_mestre.as_bytes()[..16.min(
            self.hash_senha_mestre.len()
        )];
        let chave = derivar_chave_de_senha(senha_mestre, salt);

        let dados = STANDARD.decode(&entrada.dados_cifrados)
            .map_err(|_| "Dados corrompidos")?;

        if dados.len() < NONCE_LEN + 16 {
            return Err("Dados muito curtos".to_string());
        }

        let (nonce_bytes, ciphertext_e_tag) = dados.split_at(NONCE_LEN);
        let mut nonce = [0u8; NONCE_LEN];
        nonce.copy_from_slice(nonce_bytes);

        let unbound = UnboundKey::new(&AES_256_GCM, &chave)
            .map_err(|_| "Chave inválida")?;
        let mut opening = OpeningKey::new(unbound, NonceSingle(nonce));

        let mut buffer = ciphertext_e_tag.to_vec();
        let plaintext = opening.open_in_place(
            Aad::from(nome.as_bytes()),
            &mut buffer,
        ).map_err(|_| "Senha mestre incorreta ou dados corrompidos")?;

        let senha = String::from_utf8(plaintext.to_vec())
            .map_err(|_| "Senha contém bytes inválidos")?;

        Ok(EntradaDecifrada {
            usuario: entrada.usuario.clone(),
            senha,
            notas: entrada.notas.clone(),
        })
    }

    fn salvar(&self, caminho: &str) -> std::io::Result<()> {
        let json = serde_json::to_string_pretty(self).unwrap();
        std::fs::write(caminho, json)
    }

    fn carregar(caminho: &str) -> std::io::Result<Self> {
        let json = std::fs::read_to_string(caminho)?;
        serde_json::from_str(&json)
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
    }
}

fn main() {
    let caminho_cofre = "cofre.json";
    let senha_mestre = "MinhaSenhaMestre123!";

    println!("═══════════════════════════════");
    println!("       Cofre de Senhas Rust    ");
    println!("═══════════════════════════════
");

    // Cria ou carrega cofre
    let mut cofre = if Path::new(caminho_cofre).exists() {
        println!("Carregando cofre existente...");
        Cofre::carregar(caminho_cofre).expect("Falha ao carregar")
    } else {
        println!("Criando novo cofre...");
        Cofre::novo(senha_mestre)
    };

    // Adiciona entradas
    println!("Adicionando entradas...
");

    cofre.adicionar(
        "github",
        "ana@exemplo.com",
        "s3nh4_g1thub_fort3!",
        Some("Conta pessoal"),
        senha_mestre,
    ).unwrap();

    cofre.adicionar(
        "banco",
        "12345-6",
        "senha_banco_2024",
        None,
        senha_mestre,
    ).unwrap();

    cofre.adicionar(
        "email_trabalho",
        "ana.silva@empresa.com",
        "Email@Empresa#2024",
        Some("Trocar a cada 90 dias"),
        senha_mestre,
    ).unwrap();

    // Salva
    cofre.salvar(caminho_cofre).unwrap();
    println!("Cofre salvo em '{caminho_cofre}'
");

    // Lista entradas
    println!("── Entradas no cofre ──");
    let mut nomes: Vec<&String> = cofre.entradas.keys().collect();
    nomes.sort();
    for nome in &nomes {
        let entrada = &cofre.entradas[*nome];
        println!("  • {nome} (usuário: {})", entrada.usuario);
        if let Some(ref nota) = entrada.notas {
            println!("    Nota: {nota}");
        }
    }

    // Recupera entradas
    println!("
── Recuperando senhas ──");
    for nome in &["github", "banco"] {
        match cofre.obter(nome, senha_mestre) {
            Ok(entrada) => {
                println!("
{nome}:");
                println!("  Usuário: {}", entrada.usuario);
                println!("  Senha:   {}", entrada.senha);
                if let Some(nota) = &entrada.notas {
                    println!("  Nota:    {nota}");
                }
            }
            Err(e) => println!("Erro ao recuperar '{nome}': {e}"),
        }
    }

    // Tenta com senha errada
    println!("
── Teste com senha errada ──");
    match cofre.obter("github", "senha_errada") {
        Ok(_)  => println!("Não deveria funcionar!"),
        Err(e) => println!("✓ Acesso negado: {e}"),
    }

    println!("
── Segurança ──");
    println!("• Senha mestre: nunca armazenada em texto plano");
    println!("• Cada entrada: cifrada com AES-256-GCM");
    println!("• Adulteração: detectada automaticamente");
    println!("• Chave derivada: Argon2id (resistente a GPU)");

    // Limpeza
    let _ = std::fs::remove_file(caminho_cofre);
}

Princípios de segurança ao usar criptografia

Implementar criptografia corretamente exige atenção a princípios que vão além do código:

Nunca implemente algoritmos criptográficos. Use bibliotecas auditadas — ring, rustls, argon2. Implementações caseiras de AES ou RSA quase certamente têm vulnerabilidades sutis.

Nonces nunca devem se repetir. Em AES-GCM, reutilizar um nonce com a mesma chave é catastrófico — revela tanto a chave quanto o plaintext. Sempre gere nonces aleatoriamente com OsRng.

Use comparação em tempo constante. Comparar secrets com == vaza informação via timing attacks. Use subtle::ConstantTimeEq ou as funções de verificação das bibliotecas.

Separe chaves por propósito. Não use a mesma chave para cifrar e para HMAC. Derive chaves separadas para cada propósito a partir de uma chave mestre.

Senhas nunca em texto plano. Sempre Argon2id (ou bcrypt como segunda opção). SHA-256 não é adequado para senhas — é rápido demais.

Dados sensíveis em memória. Considere usar zeroize para apagar dados sensíveis da memória quando não mais necessários.


Fontes e leituras recomendadas

  • ring crate — criptografia de baixo nível auditada — https://docs.rs/ring
  • rustls documentation — TLS em Rust puro — https://docs.rs/rustls
  • argon2 crate — hashing de senhas moderno — https://docs.rs/argon2
  • "Cryptography Engineering" — Ferguson, Schneier, Kohno — livro fundamental sobre criptografia aplicada
  • "Rust Cryptography" — RustCrypto GitHub — coleção de crates criptográficas auditadas — https://github.com/RustCrypto
  • OWASP Cryptographic Storage Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
  • subtle crate — comparações em tempo constante — https://docs.rs/subtle
  • zeroize crate — apagar dados sensíveis da memória — https://docs.rs/zeroize
Comentários

Mais em Rust

O Ecossistema Rust — Cargo, crates.io e as Bibliotecas Essenciais
O Ecossistema Rust — Cargo, crates.io e as Bibliotecas Essenciais

Nenhuma linguagem existe no vácuo. O que torna Rust produtivo além da linguag...

Gerenciamento de Dependências e Publicação de Crates — Do Projeto Local ao crates.io
Gerenciamento de Dependências e Publicação de Crates — Do Projeto Local ao crates.io

Rust — Artigo #39 Gerenciamento de Dependências e Publicação de Crates — Do P...

Structs — Criando seus próprios tipos de dados
Structs — Criando seus próprios tipos de dados

&nbsp; At&eacute; agora trabalhamos com tipos primitivos &mdash; inteiros, s...