Gerador de Senhas em Zig — Tutorial Passo a Passo
Neste tutorial, vamos construir um gerador de senhas seguras em Zig. Este projeto é ideal para explorar o gerador de números aleatórios criptográficos da stdlib, manipulação de caracteres e design de API configurável.
O Que Vamos Construir
Nosso gerador vai:
- Gerar senhas com comprimento e critérios configuráveis
- Usar entropia criptográfica do sistema operacional
- Permitir inclusão/exclusão de tipos de caracteres (maiúsculas, números, símbolos)
- Avaliar a força da senha gerada
- Gerar múltiplas senhas de uma vez
Por Que Este Projeto?
Geradores de senhas parecem triviais, mas revelam decisões importantes: como obter aleatoriedade criptográfica versus pseudo-aleatória, como garantir que a senha atenda a critérios (pelo menos um dígito, um símbolo, etc.) sem comprometer a uniformidade, e como estimar entropia. Zig expõe essas decisões de forma clara.
Passo 1: Conjuntos de Caracteres
const std = @import("std");
const crypto = std.crypto;
const io = std.io;
const fmt = std.fmt;
const mem = std.mem;
/// Configuração para geração de senhas.
/// Usamos uma struct com valores padrão para permitir
/// construção parcial — o usuário pode personalizar apenas
/// o que precisa, como no padrão Builder.
const ConfigSenha = struct {
comprimento: u32 = 16,
usar_maiusculas: bool = true,
usar_minusculas: bool = true,
usar_numeros: bool = true,
usar_simbolos: bool = true,
excluir_ambiguos: bool = false, // Exclui 0, O, l, 1, I
/// Retorna o conjunto de caracteres disponíveis baseado na configuração.
pub fn caracteres(self: ConfigSenha) []const u8 {
// Construímos o charset em comptime não é possível porque
// depende de campos runtime. Então usamos constantes pré-definidas.
const minusculas = "abcdefghijklmnopqrstuvwxyz";
const minusculas_sem_ambiguo = "abcdefghjkmnpqrstuvwxyz";
const maiusculas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const maiusculas_sem_ambiguo = "ABCDEFGHJKLMNPQRSTUVWXYZ";
const numeros = "0123456789";
const numeros_sem_ambiguo = "23456789";
const simbolos = "!@#$%^&*()-_=+[]{}|;:,.<>?";
// Para simplificar, retornamos o conjunto mais completo
// e filtramos durante a geração. Em produção, seria melhor
// construir o charset uma vez e reutilizá-lo.
if (self.excluir_ambiguos) {
if (self.usar_simbolos) return minusculas_sem_ambiguo ++ maiusculas_sem_ambiguo ++ numeros_sem_ambiguo ++ simbolos;
if (self.usar_numeros) return minusculas_sem_ambiguo ++ maiusculas_sem_ambiguo ++ numeros_sem_ambiguo;
return minusculas_sem_ambiguo ++ maiusculas_sem_ambiguo;
}
if (self.usar_simbolos) return minusculas ++ maiusculas ++ numeros ++ simbolos;
if (self.usar_numeros and self.usar_maiusculas) return minusculas ++ maiusculas ++ numeros;
if (self.usar_numeros) return minusculas ++ numeros;
return minusculas;
}
};
Passo 2: Geração com Entropia Criptográfica
/// Gera uma senha aleatória baseada na configuração.
///
/// Decisão de design: usamos std.crypto.random em vez de
/// um PRNG como DefaultPrng. Para senhas, a qualidade da
/// aleatoriedade importa — um PRNG determinístico poderia
/// gerar senhas previsíveis se a seed fosse comprometida.
fn gerarSenha(buf: []u8, config: ConfigSenha) []u8 {
const charset = config.caracteres();
const len = @min(config.comprimento, @as(u32, @intCast(buf.len)));
for (0..len) |i| {
// crypto.random é alimentado pelo OS (urandom/getrandom)
// e é criptograficamente seguro.
const idx = crypto.random.uintLessThan(usize, charset.len);
buf[i] = charset[idx];
}
return buf[0..len];
}
/// Garante que a senha contenha pelo menos um caractere de cada
/// tipo solicitado. Se não contiver, substitui posições aleatórias.
///
/// Decisão: fazemos isso DEPOIS da geração para manter a
/// uniformidade máxima. Forçar posições fixas (ex: primeiro char
/// sempre maiúsculo) reduziria a entropia efetiva.
fn garantirCriterios(senha: []u8, config: ConfigSenha) void {
if (senha.len < 4) return; // Muito curta para garantir todos os tipos
var tem_maiuscula = false;
var tem_minuscula = false;
var tem_numero = false;
var tem_simbolo = false;
for (senha) |c| {
if (std.ascii.isUpper(c)) tem_maiuscula = true;
if (std.ascii.isLower(c)) tem_minuscula = true;
if (std.ascii.isDigit(c)) tem_numero = true;
if (!std.ascii.isAlphanumeric(c)) tem_simbolo = true;
}
// Substitui posições aleatórias se necessário
if (config.usar_maiusculas and !tem_maiuscula) {
const pos = crypto.random.uintLessThan(usize, senha.len);
const maiusculas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
senha[pos] = maiusculas[crypto.random.uintLessThan(usize, maiusculas.len)];
}
if (config.usar_minusculas and !tem_minuscula) {
const pos = crypto.random.uintLessThan(usize, senha.len);
const minusculas = "abcdefghijklmnopqrstuvwxyz";
senha[pos] = minusculas[crypto.random.uintLessThan(usize, minusculas.len)];
}
if (config.usar_numeros and !tem_numero) {
const pos = crypto.random.uintLessThan(usize, senha.len);
const numeros = "0123456789";
senha[pos] = numeros[crypto.random.uintLessThan(usize, numeros.len)];
}
if (config.usar_simbolos and !tem_simbolo) {
const pos = crypto.random.uintLessThan(usize, senha.len);
const simbolos = "!@#$%^&*()-_=+";
senha[pos] = simbolos[crypto.random.uintLessThan(usize, simbolos.len)];
}
}
Passo 3: Avaliação de Força
/// Nível de força da senha.
const ForcaSenha = enum {
fraca,
razoavel,
forte,
muito_forte,
pub fn descricao(self: ForcaSenha) []const u8 {
return switch (self) {
.fraca => "Fraca",
.razoavel => "Razoável",
.forte => "Forte",
.muito_forte => "Muito Forte",
};
}
pub fn barra(self: ForcaSenha) []const u8 {
return switch (self) {
.fraca => "[##--------]",
.razoavel => "[#####-----]",
.forte => "[########--]",
.muito_forte => "[##########]",
};
}
};
/// Calcula a entropia em bits de uma senha.
/// Entropia = log2(charset_size ^ length) = length * log2(charset_size)
fn calcularEntropia(senha: []const u8) f64 {
var tem_lower = false;
var tem_upper = false;
var tem_digit = false;
var tem_symbol = false;
for (senha) |c| {
if (std.ascii.isLower(c)) tem_lower = true;
if (std.ascii.isUpper(c)) tem_upper = true;
if (std.ascii.isDigit(c)) tem_digit = true;
if (!std.ascii.isAlphanumeric(c)) tem_symbol = true;
}
var tamanho_charset: f64 = 0;
if (tem_lower) tamanho_charset += 26;
if (tem_upper) tamanho_charset += 26;
if (tem_digit) tamanho_charset += 10;
if (tem_symbol) tamanho_charset += 30;
if (tamanho_charset == 0) return 0;
const len_f: f64 = @floatFromInt(senha.len);
return len_f * @log2(tamanho_charset);
}
/// Avalia a força da senha baseado na entropia.
fn avaliarForca(senha: []const u8) ForcaSenha {
const entropia = calcularEntropia(senha);
if (entropia < 40) return .fraca;
if (entropia < 60) return .razoavel;
if (entropia < 80) return .forte;
return .muito_forte;
}
Passo 4: Interface Principal
pub fn main() !void {
const stdout = io.getStdOut().writer();
const stdin = io.getStdIn().reader();
try stdout.print(
\\
\\ Gerador de Senhas Zig v1.0
\\ ══════════════════════════
\\
, .{});
var buf_entrada: [256]u8 = undefined;
var buf_senha: [128]u8 = undefined;
while (true) {
try stdout.print(
\\
\\ [1] Gerar senha padrão (16 chars, todos os tipos)
\\ [2] Gerar senha personalizada
\\ [3] Gerar múltiplas senhas
\\ [4] Sair
\\
\\ Opção:
, .{});
const linha = stdin.readUntilDelimiterOrEof(&buf_entrada, '\n') catch continue orelse break;
const opcao = mem.trim(u8, linha, " \t\r\n");
if (mem.eql(u8, opcao, "4") or mem.eql(u8, opcao, "sair")) break;
if (mem.eql(u8, opcao, "1")) {
const config = ConfigSenha{};
const senha = gerarSenha(&buf_senha, config);
garantirCriterios(senha, config);
const forca = avaliarForca(senha);
try stdout.print("\n Senha: {s}\n", .{senha});
try stdout.print(" Força: {s} {s}\n", .{ forca.barra(), forca.descricao() });
try stdout.print(" Entropia: {d:.1} bits\n", .{calcularEntropia(senha)});
} else if (mem.eql(u8, opcao, "2")) {
try stdout.print(" Comprimento (8-64): ", .{});
const comp_linha = stdin.readUntilDelimiterOrEof(&buf_entrada, '\n') catch continue orelse continue;
const comp = fmt.parseInt(u32, mem.trim(u8, comp_linha, " \t\r\n"), 10) catch {
try stdout.print(" Número inválido.\n", .{});
continue;
};
if (comp < 8 or comp > 64) {
try stdout.print(" Comprimento deve ser entre 8 e 64.\n", .{});
continue;
}
const config = ConfigSenha{
.comprimento = comp,
.usar_simbolos = true,
.usar_numeros = true,
.usar_maiusculas = true,
};
const senha = gerarSenha(&buf_senha, config);
garantirCriterios(senha, config);
const forca = avaliarForca(senha);
try stdout.print("\n Senha: {s}\n", .{senha});
try stdout.print(" Força: {s} {s}\n", .{ forca.barra(), forca.descricao() });
try stdout.print(" Entropia: {d:.1} bits\n", .{calcularEntropia(senha)});
} else if (mem.eql(u8, opcao, "3")) {
try stdout.print(" Quantas senhas (1-20): ", .{});
const qtd_linha = stdin.readUntilDelimiterOrEof(&buf_entrada, '\n') catch continue orelse continue;
const qtd = fmt.parseInt(u32, mem.trim(u8, qtd_linha, " \t\r\n"), 10) catch {
try stdout.print(" Número inválido.\n", .{});
continue;
};
if (qtd < 1 or qtd > 20) {
try stdout.print(" Quantidade deve ser entre 1 e 20.\n", .{});
continue;
}
const config = ConfigSenha{};
try stdout.print("\n", .{});
var i: u32 = 0;
while (i < qtd) : (i += 1) {
const senha = gerarSenha(&buf_senha, config);
garantirCriterios(senha, config);
try stdout.print(" {d:>2}. {s}\n", .{ i + 1, senha });
}
}
}
try stdout.print("\nAté logo!\n", .{});
}
Passo 5: Testes
test "senha gerada tem comprimento correto" {
var buf: [128]u8 = undefined;
const config = ConfigSenha{ .comprimento = 20 };
const senha = gerarSenha(&buf, config);
try std.testing.expectEqual(@as(usize, 20), senha.len);
}
test "entropia de senha forte" {
const entropia = calcularEntropia("aB3!xY9@kL2#");
try std.testing.expect(entropia > 60);
}
test "avaliação de força" {
try std.testing.expectEqual(ForcaSenha.fraca, avaliarForca("abc"));
try std.testing.expectEqual(ForcaSenha.muito_forte, avaliarForca("aB3!xY9@kL2#mN5$pQ7&"));
}
test "charset varia com configuração" {
const config_simples = ConfigSenha{ .usar_simbolos = false, .usar_numeros = false, .usar_maiusculas = false };
const config_completa = ConfigSenha{};
try std.testing.expect(config_completa.caracteres().len > config_simples.caracteres().len);
}
Compilando e Executando
zig build test
zig build run
Conceitos Aprendidos
- Uso de
std.crypto.randompara aleatoriedade criptográfica - Structs com valores padrão como padrão Builder
- Cálculo de entropia e segurança de senhas
- Manipulação de caracteres ASCII com
std.ascii - Design de API configurável
Próximos Passos
- Aprenda sobre criptografia em Zig para projetos de segurança
- Explore o módulo std.crypto da biblioteca padrão
- Construa o próximo projeto: Jogo de Adivinhação