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
ringcrate — criptografia de baixo nível auditada — https://docs.rs/ringrustlsdocumentation — TLS em Rust puro — https://docs.rs/rustlsargon2crate — 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
subtlecrate — comparações em tempo constante — https://docs.rs/subtlezeroizecrate — apagar dados sensíveis da memória — https://docs.rs/zeroize