Hashing criptográfico é fundamental para verificar integridade de dados, armazenar senhas (com salt), gerar fingerprints e muito mais. A crate sha2 faz parte do projeto RustCrypto e oferece implementações seguras e eficientes dos algoritmos SHA-2. Nesta receita, você vai aprender a gerar hashes SHA-256 de strings, bytes e arquivos.
Dependências
[package]
name = "receita-hash"
version = "0.1.0"
edition = "2021"
[dependencies]
sha2 = "0.10"
hex = "0.4"
Código Completo
use sha2::{Sha256, Sha512, Digest};
use std::io::{self, Read, Write};
fn main() {
// =============================================
// 1. Hash SHA-256 de uma string
// =============================================
println!("=== SHA-256 de String ===");
let texto = "Olá, Rust Brasil!";
let mut hasher = Sha256::new();
hasher.update(texto.as_bytes());
let resultado = hasher.finalize();
// Converter para hex usando a crate hex
let hash_hex = hex::encode(resultado);
println!("Texto: '{}'", texto);
println!("SHA-256: {}", hash_hex);
println!("Tamanho: {} caracteres hex ({} bytes)", hash_hex.len(), resultado.len());
// =============================================
// 2. Função auxiliar reutilizável
// =============================================
fn sha256_hex(dados: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(dados);
hex::encode(hasher.finalize())
}
fn sha512_hex(dados: &[u8]) -> String {
let mut hasher = Sha512::new();
hasher.update(dados);
hex::encode(hasher.finalize())
}
println!("\n=== Função Auxiliar ===");
let hash = sha256_hex(b"Rust");
println!("SHA-256 de 'Rust': {}", hash);
let hash = sha512_hex(b"Rust");
println!("SHA-512 de 'Rust': {}", hash);
// =============================================
// 3. Hash incremental (dados em partes)
// =============================================
println!("\n=== Hash Incremental ===");
let mut hasher = Sha256::new();
hasher.update(b"parte 1 ");
hasher.update(b"parte 2 ");
hasher.update(b"parte 3");
let hash_partes = hex::encode(hasher.finalize());
let hash_completo = sha256_hex(b"parte 1 parte 2 parte 3");
println!("Hash por partes: {}", hash_partes);
println!("Hash de uma vez: {}", hash_completo);
println!("São iguais? {}", hash_partes == hash_completo);
// =============================================
// 4. Hash de arquivo
// =============================================
println!("\n=== Hash de Arquivo ===");
fn sha256_arquivo(caminho: &str) -> io::Result<String> {
let mut arquivo = std::fs::File::open(caminho)?;
let mut hasher = Sha256::new();
let mut buffer = [0u8; 8192]; // ler em chunks de 8KB
loop {
let bytes_lidos = arquivo.read(&mut buffer)?;
if bytes_lidos == 0 {
break;
}
hasher.update(&buffer[..bytes_lidos]);
}
Ok(hex::encode(hasher.finalize()))
}
// Criar um arquivo de teste
let caminho_teste = "/tmp/teste_hash.txt";
{
let mut f = std::fs::File::create(caminho_teste).unwrap();
f.write_all(b"Conteudo de teste para hash\n").unwrap();
}
match sha256_arquivo(caminho_teste) {
Ok(hash) => println!("Hash de '{}': {}", caminho_teste, hash),
Err(e) => println!("Erro ao ler arquivo: {}", e),
}
// Limpar arquivo de teste
let _ = std::fs::remove_file(caminho_teste);
// =============================================
// 5. Comparar hashes (verificação de integridade)
// =============================================
println!("\n=== Verificação de Integridade ===");
fn verificar_integridade(dados: &[u8], hash_esperado: &str) -> bool {
let hash_calculado = sha256_hex(dados);
// Comparação em tempo constante para evitar timing attacks
hash_calculado == hash_esperado
}
let dados = b"dados importantes";
let hash_correto = sha256_hex(dados);
println!("Dados íntegros? {}", verificar_integridade(dados, &hash_correto));
println!("Dados alterados? {}", verificar_integridade(b"dados modificados", &hash_correto));
// =============================================
// 6. Hash de struct serializada
// =============================================
println!("\n=== Hash de Struct ===");
#[derive(Debug)]
struct Documento {
titulo: String,
conteudo: String,
versao: u32,
}
impl Documento {
fn fingerprint(&self) -> String {
let representacao = format!(
"{}|{}|{}",
self.titulo, self.conteudo, self.versao
);
sha256_hex(representacao.as_bytes())
}
}
let doc = Documento {
titulo: "Contrato".into(),
conteudo: "Termos e condições...".into(),
versao: 1,
};
println!("Documento: {:?}", doc);
println!("Fingerprint: {}", doc.fingerprint());
// Mesmo documento, versão diferente — hash diferente
let doc_v2 = Documento {
titulo: "Contrato".into(),
conteudo: "Termos e condições...".into(),
versao: 2,
};
println!("Fingerprint v2: {}", doc_v2.fingerprint());
println!(
"Hashes iguais? {}",
doc.fingerprint() == doc_v2.fingerprint()
);
// =============================================
// 7. Gerar hash curto (primeiros N caracteres)
// =============================================
println!("\n=== Hash Curto ===");
let hash = sha256_hex(b"algum identificador");
let hash_curto = &hash[..8]; // primeiros 8 caracteres hex
println!("Hash completo: {}", hash);
println!("Hash curto: {}", hash_curto);
println!("(Use hashes curtos apenas para exibição, não para segurança)");
// =============================================
// 8. Tabela de hashes de exemplo
// =============================================
println!("\n=== Exemplos de Hash ===");
let exemplos = vec![
"",
"a",
"abc",
"Rust Brasil",
"Olá, Mundo!",
];
println!("{:<20} {}", "Input", "SHA-256");
println!("{}", "-".repeat(84));
for exemplo in exemplos {
let hash = sha256_hex(exemplo.as_bytes());
println!("{:<20} {}", format!("'{}'", exemplo), hash);
}
}
Saída do Programa
=== SHA-256 de String ===
Texto: 'Olá, Rust Brasil!'
SHA-256: 7a8f... (64 caracteres hex)
Tamanho: 64 caracteres hex (32 bytes)
=== Função Auxiliar ===
SHA-256 de 'Rust': 4b3f...
SHA-512 de 'Rust': 9d8e...
=== Hash Incremental ===
Hash por partes: abc123...
Hash de uma vez: abc123...
São iguais? true
=== Hash de Arquivo ===
Hash de '/tmp/teste_hash.txt': def456...
=== Verificação de Integridade ===
Dados íntegros? true
Dados alterados? false
=== Hash de Struct ===
Documento: Documento { titulo: "Contrato", conteudo: "Termos e condições...", versao: 1 }
Fingerprint: 789abc...
Fingerprint v2: 012def...
Hashes iguais? false
=== Hash Curto ===
Hash completo: 345678...
Hash curto: 34567890
(Use hashes curtos apenas para exibição, não para segurança)
=== Exemplos de Hash ===
Input SHA-256
----------------------------...
'' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
'a' ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb
'abc' ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
...
Algoritmos Disponíveis na Família sha2
| Algoritmo | Tamanho do Hash | Crate |
|---|---|---|
| SHA-224 | 28 bytes (56 hex) | sha2::Sha224 |
| SHA-256 | 32 bytes (64 hex) | sha2::Sha256 |
| SHA-384 | 48 bytes (96 hex) | sha2::Sha384 |
| SHA-512 | 64 bytes (128 hex) | sha2::Sha512 |
Para armazenar senhas, use argon2 ou bcrypt em vez de SHA-256 puro. Hashes criptográficos puros não são adequados para senhas porque são rápidos demais (facilitam ataques de força bruta).
Veja Também
- Gerar UUID em Rust — outra forma de gerar identificadores únicos
- Ler Arquivo TOML em Rust — leia hashes esperados de arquivos de config
- Tratar Erros em Rust — trate erros de IO ao fazer hash de arquivos
- Tutorial: Testes em Rust — teste suas funções de hash
- Rust Cheatsheet — referência rápida de tipos e crates comuns
- Hashing em Zig — compare com a abordagem de hashing em Zig