Introdução
Funções de hash criptográficas transformam dados de qualquer tamanho em uma sequência de bytes de tamanho fixo. São usadas para verificação de integridade, armazenamento de senhas, assinaturas digitais e deduplicação. Zig oferece implementações de SHA-256, SHA-512, MD5 e outros na biblioteca padrão via std.crypto.hash.
Nesta receita, você aprenderá a calcular hashes dos tipos mais comuns.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
SHA-256 Básico
const std = @import("std");
const Sha256 = std.crypto.hash.sha2.Sha256;
pub fn main() !void {
const texto = "Olá, Zig Brasil!";
// Calcular hash
var hash: [Sha256.digest_length]u8 = undefined;
Sha256.hash(texto, &hash, .{});
// Exibir em hexadecimal
std.debug.print("Texto: {s}\n", .{texto});
std.debug.print("SHA-256: {s}\n", .{std.fmt.fmtSliceHexLower(&hash)});
}
MD5
const std = @import("std");
const Md5 = std.crypto.hash.Md5;
pub fn main() !void {
const texto = "Olá, Zig Brasil!";
var hash: [Md5.digest_length]u8 = undefined;
Md5.hash(texto, &hash, .{});
std.debug.print("Texto: {s}\n", .{texto});
std.debug.print("MD5: {s}\n", .{std.fmt.fmtSliceHexLower(&hash)});
std.debug.print("Tamanho do digest: {d} bytes ({d} bits)\n", .{ hash.len, hash.len * 8 });
}
Múltiplos Algoritmos
const std = @import("std");
pub fn main() !void {
const texto = "Zig é incrível!";
// SHA-256
var sha256: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined;
std.crypto.hash.sha2.Sha256.hash(texto, &sha256, .{});
// SHA-512
var sha512: [std.crypto.hash.sha2.Sha512.digest_length]u8 = undefined;
std.crypto.hash.sha2.Sha512.hash(texto, &sha512, .{});
// MD5
var md5: [std.crypto.hash.Md5.digest_length]u8 = undefined;
std.crypto.hash.Md5.hash(texto, &md5, .{});
std.debug.print("Texto: {s}\n\n", .{texto});
std.debug.print("MD5: {s}\n", .{std.fmt.fmtSliceHexLower(&md5)});
std.debug.print(" ({d} bits)\n\n", .{md5.len * 8});
std.debug.print("SHA256: {s}\n", .{std.fmt.fmtSliceHexLower(&sha256)});
std.debug.print(" ({d} bits)\n\n", .{sha256.len * 8});
std.debug.print("SHA512: {s}\n", .{std.fmt.fmtSliceHexLower(&sha512)});
std.debug.print(" ({d} bits)\n", .{sha512.len * 8});
}
Hash Incremental (Streaming)
Para dados que chegam em partes:
const std = @import("std");
const Sha256 = std.crypto.hash.sha2.Sha256;
pub fn main() !void {
// Hash incremental
var hasher = Sha256.init(.{});
hasher.update("Parte 1: ");
hasher.update("Olá, ");
hasher.update("Zig ");
hasher.update("Brasil!");
var hash: [Sha256.digest_length]u8 = undefined;
hasher.final(&hash);
std.debug.print("Hash incremental: {s}\n", .{std.fmt.fmtSliceHexLower(&hash)});
// Verificar que é igual ao hash direto
var hash_direto: [Sha256.digest_length]u8 = undefined;
Sha256.hash("Parte 1: Olá, Zig Brasil!", &hash_direto, .{});
std.debug.print("Hash direto: {s}\n", .{std.fmt.fmtSliceHexLower(&hash_direto)});
std.debug.print("São iguais: {}\n", .{std.mem.eql(u8, &hash, &hash_direto)});
}
Hash de Arquivo
const std = @import("std");
const Sha256 = std.crypto.hash.sha2.Sha256;
fn hashDeArquivo(caminho: []const u8) ![Sha256.digest_length]u8 {
const arquivo = try std.fs.cwd().openFile(caminho, .{});
defer arquivo.close();
var hasher = Sha256.init(.{});
var buf: [4096]u8 = undefined;
while (true) {
const n = try arquivo.read(&buf);
if (n == 0) break;
hasher.update(buf[0..n]);
}
var hash: [Sha256.digest_length]u8 = undefined;
hasher.final(&hash);
return hash;
}
pub fn main() !void {
// Criar arquivo de teste
const cwd = std.fs.cwd();
{
const f = try cwd.createFile("teste_hash.txt", .{});
defer f.close();
try f.writeAll("Conteúdo do arquivo para teste de hash.\n");
}
defer cwd.deleteFile("teste_hash.txt") catch {};
const hash = try hashDeArquivo("teste_hash.txt");
std.debug.print("SHA-256 de teste_hash.txt:\n{s}\n", .{std.fmt.fmtSliceHexLower(&hash)});
}
Verificar Integridade
const std = @import("std");
const Sha256 = std.crypto.hash.sha2.Sha256;
fn verificarIntegridade(dados: []const u8, hash_esperado: []const u8) bool {
var hash: [Sha256.digest_length]u8 = undefined;
Sha256.hash(dados, &hash, .{});
var hash_hex: [Sha256.digest_length * 2]u8 = undefined;
_ = std.fmt.bufPrint(&hash_hex, "{s}", .{std.fmt.fmtSliceHexLower(&hash)}) catch return false;
return std.mem.eql(u8, &hash_hex, hash_esperado);
}
pub fn main() !void {
const dados = "dados importantes";
// Calcular hash
var hash: [Sha256.digest_length]u8 = undefined;
Sha256.hash(dados, &hash, .{});
var hash_hex: [64]u8 = undefined;
_ = try std.fmt.bufPrint(&hash_hex, "{s}", .{std.fmt.fmtSliceHexLower(&hash)});
std.debug.print("Hash original: {s}\n", .{hash_hex});
// Verificar dados originais
std.debug.print("Verificação (original): {}\n", .{
verificarIntegridade(dados, &hash_hex),
});
// Verificar dados alterados
std.debug.print("Verificação (alterado): {}\n", .{
verificarIntegridade("dados modificados", &hash_hex),
});
}
Dicas e Boas Práticas
Não use MD5 para segurança: MD5 tem colisões conhecidas. Use SHA-256 ou SHA-512 para fins criptográficos.
Hash incremental para dados grandes: Use
init/update/finalpara arquivos grandes sem carregar tudo na memória.fmtSliceHexLower: A forma mais simples de converter hash em string hexadecimal.Hash de senhas: Para senhas, use bcrypt ou Argon2 (não SHA ou MD5 diretamente).
Receitas Relacionadas
- Base64 Encoding/Decoding - Codificação Base64
- Hex Encoding/Decoding - Hex para exibir hashes
- Ler Conteúdo de Arquivo - Hash de arquivos
- Compressão com Zlib - Compressão de dados