sha2 / bcrypt para Rust: guia completo com exemplos
No desenvolvimento moderno, a segurança dos dados é um aspecto crítico de qualquer aplicação. As bibliotecas sha2 e bcrypt para a linguagem Rust fornecem ferramentas poderosas e seguras para hashing criptográfico. sha2 implementa a família de algoritmos SHA-2 (Secure Hash Algorithm 2), incluindo SHA-224, SHA-256, SHA-384, SHA-512, bem como suas variantes truncadas (SHA-512/224, SHA-512/256). bcrypt é uma função de hash criptográfico adaptável, projetada especificamente para armazenar senhas de forma segura. Ela usa "salt" e um número ajustável de rodadas (cost factor), tornando-a resistente a ataques de tabelas arco-íris e força bruta.
Por que isso é necessário? O SHA-2 é usado para verificar a integridade de dados, criar assinaturas digitais, em sistemas de controle de versão (Git), blockchain e muitas outras áreas que exigem um hash rápido e confiável. O Bcrypt, por sua vez, é o padrão de facto para hashing de senhas em aplicações web, bancos de dados e sistemas de autenticação. Sua principal vantagem é a "lentidão" ajustável: você pode aumentar o número de rodadas para compensar o aumento do poder computacional dos invasores.
Neste artigo, vamos explorar detalhadamente ambas as bibliotecas: instalação, métodos principais, exemplos reais de uso e erros comuns. Você aprenderá como fazer hash de dados corretamente, verificar senhas e integrar essas funções em seus projetos Rust.
Instalação
Para usar sha2 e bcrypt no Rust, você precisa adicioná-los ao arquivo Cargo.toml do seu projeto. Abra o terminal no diretório raiz do projeto e execute:
cargo add sha2 bcrypt
Este comando adicionará as versões mais recentes das bibliotecas na seção [dependencies]. Alternativamente, você pode editar manualmente o Cargo.toml:
[dependencies]
sha2 = "0.10"
bcrypt = "0.15"
Depois disso, execute cargo build para baixar as dependências e verificar se tudo foi instalado corretamente.
Início rápido: exemplo funcional mínimo
Vamos começar com um exemplo simples: fazer hash de uma string com SHA-256 e verificar uma senha com bcrypt.
use sha2::{Sha256, Digest};
use bcrypt::{hash, verify, DEFAULT_COST};
fn main() {
// Hashing SHA-256
let data = b"Hello, world!";
let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();
println!("Hash SHA-256: {:x}", result);
// Hashing de senha com Bcrypt
let password = "minha_senha_segura";
let hashed = hash(password, DEFAULT_COST).unwrap();
println!("Hash Bcrypt: {}", hashed);
// Verificação de senha
let is_valid = verify(password, &hashed).unwrap();
println!("Senha correta: {}", is_valid);
}
Este código demonstra as funcionalidades básicas de ambas as bibliotecas. A saída conterá um hash hex SHA-256 de 64 caracteres e uma longa string bcrypt (começando com $2b$).
Métodos e funções principais
Vamos analisar as principais funções e métodos do sha2 e bcrypt. Para cada um, fornecemos a assinatura, descrição e um exemplo funcional.
1. sha2::Sha256::new()
Assinatura: pub fn new() -> Sha256
Descrição: Cria uma nova instância do hasher SHA-256. Este é o ponto de partida para fazer hash de dados.
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(b"dados de teste");
let hash = hasher.finalize();
println!("{:x}", hash);
2. sha2::Sha256::update()
Assinatura: pub fn update(&mut self, data: impl AsRef<[u8]>)
Descrição: Alimenta o hasher com dados adicionais para processamento. Pode ser chamado várias vezes para processar dados em partes.
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(b"primeira parte ");
hasher.update(b"segunda parte");
let hash = hasher.finalize();
println!("{:x}", hash);
3. sha2::Sha256::finalize()
Assinatura: pub fn finalize(self) -> GenericArray<u8, U32>
Descrição: Finaliza o processo de hashing e retorna o hash resultante como um array de bytes. O tipo GenericArray pode ser convertido para outros formatos, como hex.
use sha2::{Sha256, Digest};
use hex;
let mut hasher = Sha256::new();
hasher.update(b"dados");
let result = hasher.finalize();
let hex_string = hex::encode(result);
println!("Hash em hex: {}", hex_string);
4. bcrypt::hash()
Assinatura: pub fn hash<S: AsRef<[u8]>>(password: S, cost: u32) -> Result<String, BcryptError>
Descrição: Gera um hash bcrypt para a senha fornecida, usando o custo (cost) especificado. O custo padrão (DEFAULT_COST) é 12, que oferece um bom equilíbrio entre segurança e desempenho.
use bcrypt::{hash, DEFAULT_COST};
let senha = "minha_senha";
match hash(senha, DEFAULT_COST) {
Ok(hashed) => println!("Hash gerado: {}", hashed),
Err(e) => eprintln!("Erro ao gerar hash: {}", e),
}
5. bcrypt::verify()
Assinatura: pub fn verify<S: AsRef<[u8]>>(password: S, hash: &str) -> Result<bool, BcryptError>
Descrição: Verifica se uma senha corresponde a um hash bcrypt existente. Retorna true se a senha for válida, false caso contrário.
use bcrypt::{hash, verify, DEFAULT_COST};
let senha = "minha_senha";
let hashed = hash(senha, DEFAULT_COST).unwrap();
// Verificação correta
assert!(verify(senha, &hashed).unwrap());
// Verificação incorreta
assert!(!verify("senha_errada", &hashed).unwrap());
Exemplos práticos
Exemplo 1: Hashing de arquivos com SHA-256
Este exemplo mostra como calcular o hash SHA-256 de um arquivo, útil para verificar integridade de downloads.
use sha2::{Sha256, Digest};
use std::fs::File;
use std::io::{BufReader, Read};
fn hash_arquivo(caminho: &str) -> Result<String, std::io::Error> {
let arquivo = File::open(caminho)?;
let mut leitor = BufReader::new(arquivo);
let mut hasher = Sha256::new();
let mut buffer = [0; 1024];
loop {
let bytes_lidos = leitor.read(&mut buffer)?;
if bytes_lidos == 0 {
break;
}
hasher.update(&buffer[..bytes_lidos]);
}
Ok(format!("{:x}", hasher.finalize()))
}
fn main() {
match hash_arquivo("exemplo.txt") {
Ok(hash) => println!("Hash SHA-256 do arquivo: {}", hash),
Err(e) => eprintln!("Erro: {}", e),
}
}
Exemplo 2: Sistema de registro e login com bcrypt
Um exemplo completo de como armazenar e verificar senhas de usuários usando bcrypt.
use bcrypt::{hash, verify, DEFAULT_COST};
use std::collections::HashMap;
struct Usuario {
nome: String,
hash_senha: String,
}
fn registrar(usuarios: &mut HashMap<String, Usuario>, nome: &str, senha: &str) -> Result<(), String> {
if usuarios.contains_key(nome) {
return Err("Usuário já existe".to_string());
}
let hash_senha = hash(senha, DEFAULT_COST).map_err(|e| e.to_string())?;
usuarios.insert(nome.to_string(), Usuario {
nome: nome.to_string(),
hash_senha,
});
Ok(())
}
fn login(usuarios: &HashMap<String, Usuario>, nome: &str, senha: &str) -> Result<bool, String> {
let usuario = usuarios.get(nome).ok_or("Usuário não encontrado".to_string())?;
verify(senha, &usuario.hash_senha).map_err(|e| e.to_string())
}
fn main() {
let mut usuarios: HashMap<String, Usuario> = HashMap::new();
// Registro
registrar(&mut usuarios, "joao", "senha123").unwrap();
registrar(&mut usuarios, "maria", "outra_senha").unwrap();
// Login bem-sucedido
match login(&usuarios, "joao", "senha123") {
Ok(valido) => println!("Login do João: {}", if valido { "sucesso" } else { "falha" }),
Err(e) => println!("Erro: {}", e),
}
// Login com senha errada
match login(&usuarios, "joao", "senha_errada") {
Ok(valido) => println!("Login do João (senha errada): {}", if valido { "sucesso" } else { "falha" }),
Err(e) => println!("Erro: {}", e),
}
}
Erros comuns e boas práticas
- Não usar salt: O bcrypt já inclui salt automaticamente, mas ao usar SHA-2 diretamente, sempre adicione um salt único para cada hash.
- Custo muito baixo: Use pelo menos o custo padrão (12) para bcrypt. Em sistemas críticos, considere valores mais altos (13-15).
- Armazenar senhas em texto puro: Nunca armazene senhas sem hash. Sempre use bcrypt ou algoritmos similares.
- Ignorar erros: Sempre trate os erros retornados por
hash()everify(). - Usar SHA-2 para senhas: SHA-2 é muito rápido para ser seguro para senhas. Use bcrypt, scrypt ou Argon2.
Conclusão
Neste guia, exploramos as bibliotecas sha2 e bcrypt para Rust, desde a instalação até exemplos práticos. O SHA-2 é ideal para verificação de integridade e assinaturas digitais, enquanto o bcrypt é a escolha certa para armazenamento seguro de senhas. Lembre-se sempre de seguir as boas práticas de segurança e manter suas dependências atualizadas.
Para projetos reais, considere também explorar outras bibliotecas como argon2 (para hashing de senhas mais moderno) e hmac (para autenticação de mensagens). A segurança é um processo contínuo, e escolher as ferramentas certas é o primeiro passo.
Também na biblioteca
Trabalho com datas em Java: java.time e Joda-Time
Internacionalização (i18n) em TypeScript: biblioteca i18next
NetworkExtension Swift: Guia completo com exemplos práticos
Validação de dados em PHP com a biblioteca Respect\\Validation