Como Calcular Hashes SHA256 e MD5 em Zig

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

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

  1. Não use MD5 para segurança: MD5 tem colisões conhecidas. Use SHA-256 ou SHA-512 para fins criptográficos.

  2. Hash incremental para dados grandes: Use init/update/final para arquivos grandes sem carregar tudo na memória.

  3. fmtSliceHexLower: A forma mais simples de converter hash em string hexadecimal.

  4. Hash de senhas: Para senhas, use bcrypt ou Argon2 (não SHA ou MD5 diretamente).

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.