sha2 / bcrypt para Rust: guia completo com exemplos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

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() e verify().
  • 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.

Recommendations