Senhas fracas são uma das maiores vulnerabilidades de segurança no mundo digital. Neste projeto, vamos construir um gerador de senhas robusto que permite configurar o comprimento, os conjuntos de caracteres e a quantidade de senhas geradas. Além disso, implementaremos um cálculo de entropia e um medidor de força para que o usuário saiba exatamente o quão segura é a senha gerada.
Este projeto é uma excelente introdução à geração de números aleatórios criptograficamente seguros em Rust, ao uso de iteradores para compor conjuntos de caracteres e ao design de interfaces CLI informativas.
O Que Vamos Construir
Nosso gerar-senha terá os seguintes recursos:
- Geração de senhas com comprimento configurável
- Seleção de conjuntos de caracteres (letras, números, símbolos)
- Geração de múltiplas senhas de uma vez
- Cálculo de entropia em bits
- Medidor visual de força da senha
- Modo frase-senha (passphrase) com palavras aleatórias
- Opção para evitar caracteres ambíguos (0/O, 1/l/I)
Estrutura do Projeto
gerar-senha/
├── Cargo.toml
└── src/
├── main.rs
├── cli.rs
├── gerador.rs
└── avaliador.rs
Configurando o Projeto
cargo new gerar-senha
cd gerar-senha
Configure o Cargo.toml:
[package]
name = "gerar-senha"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
rand = "0.8"
colored = "2"
A crate rand fornece geração de números aleatórios criptograficamente seguros através do trait CryptoRng, essencial para gerar senhas que não possam ser previstas.
Passo 1: Interface de Linha de Comando
// src/cli.rs
use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(name = "gerar-senha")]
#[command(about = "Gerador de senhas seguras com cálculo de entropia")]
pub struct Cli {
#[command(subcommand)]
pub comando: Comando,
}
#[derive(Subcommand, Debug)]
pub enum Comando {
/// Gera uma ou mais senhas aleatórias
Gerar {
/// Comprimento da senha
#[arg(short = 'c', long, default_value_t = 16)]
comprimento: usize,
/// Quantidade de senhas a gerar
#[arg(short = 'n', long, default_value_t = 1)]
quantidade: usize,
/// Incluir letras maiúsculas
#[arg(short = 'M', long, default_value_t = true)]
maiusculas: bool,
/// Incluir letras minúsculas
#[arg(short = 'm', long, default_value_t = true)]
minusculas: bool,
/// Incluir dígitos numéricos
#[arg(short = 'd', long, default_value_t = true)]
digitos: bool,
/// Incluir símbolos especiais
#[arg(short = 's', long, default_value_t = true)]
simbolos: bool,
/// Excluir caracteres ambíguos (0, O, l, 1, I)
#[arg(long)]
sem_ambiguos: bool,
/// Caracteres personalizados para usar
#[arg(long)]
caracteres: Option<String>,
},
/// Gera uma frase-senha com palavras aleatórias
Frase {
/// Quantidade de palavras
#[arg(short = 'p', long, default_value_t = 4)]
palavras: usize,
/// Separador entre palavras
#[arg(short = 's', long, default_value = "-")]
separador: String,
/// Capitalizar primeira letra de cada palavra
#[arg(short = 'c', long)]
capitalizar: bool,
/// Adicionar número aleatório ao final
#[arg(short = 'n', long)]
com_numero: bool,
},
/// Avalia a força de uma senha fornecida
Avaliar {
/// A senha a ser avaliada
senha: String,
},
}
Passo 2: Motor de Geração de Senhas
// src/gerador.rs
use rand::seq::SliceRandom;
use rand::Rng;
const MINUSCULAS: &str = "abcdefghijklmnopqrstuvwxyz";
const MAIUSCULAS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const DIGITOS: &str = "0123456789";
const SIMBOLOS: &str = "!@#$%^&*()-_=+[]{}|;:,.<>?/~`";
const AMBIGUOS: &[char] = &['0', 'O', 'o', 'l', '1', 'I'];
/// Lista de palavras em português para gerar frases-senha
const PALAVRAS: &[&str] = &[
"abacaxi", "banana", "cavalo", "diamante", "elefante",
"floresta", "guitarra", "horizonte", "iguana", "janela",
"lagarto", "montanha", "nuvem", "oceano", "palmeira",
"quetzal", "raposa", "serpente", "trovao", "universo",
"vulcao", "xadrez", "zebra", "ancora", "borboleta",
"cascata", "duende", "estrela", "falcao", "girassol",
"harpa", "inverno", "jardim", "lanterna", "morcego",
"nogueira", "orquestra", "pirata", "raio", "sombra",
"tempestade", "uva", "viagem", "cristal", "dragao",
"espelho", "ferradura", "golfinho", "teclado", "relógio",
];
/// Opções de configuração para o gerador
pub struct ConfigGeracao {
pub comprimento: usize,
pub maiusculas: bool,
pub minusculas: bool,
pub digitos: bool,
pub simbolos: bool,
pub sem_ambiguos: bool,
pub caracteres_personalizados: Option<String>,
}
/// Constrói o conjunto de caracteres com base na configuração
pub fn construir_charset(config: &ConfigGeracao) -> Vec<char> {
if let Some(ref personalizado) = config.caracteres_personalizados {
return personalizado.chars().collect();
}
let mut charset = String::new();
if config.minusculas {
charset.push_str(MINUSCULAS);
}
if config.maiusculas {
charset.push_str(MAIUSCULAS);
}
if config.digitos {
charset.push_str(DIGITOS);
}
if config.simbolos {
charset.push_str(SIMBOLOS);
}
let mut chars: Vec<char> = charset.chars().collect();
if config.sem_ambiguos {
chars.retain(|c| !AMBIGUOS.contains(c));
}
chars
}
/// Gera uma senha aleatória usando o charset fornecido
pub fn gerar_senha(charset: &[char], comprimento: usize) -> String {
let mut rng = rand::thread_rng();
(0..comprimento)
.map(|_| {
let idx = rng.gen_range(0..charset.len());
charset[idx]
})
.collect()
}
/// Gera uma frase-senha com palavras aleatórias
pub fn gerar_frase(
num_palavras: usize,
separador: &str,
capitalizar: bool,
com_numero: bool,
) -> String {
let mut rng = rand::thread_rng();
let palavras_escolhidas: Vec<String> = PALAVRAS
.choose_multiple(&mut rng, num_palavras)
.map(|&palavra| {
if capitalizar {
let mut chars = palavra.chars();
match chars.next() {
None => String::new(),
Some(c) => c.to_uppercase().to_string() + chars.as_str(),
}
} else {
palavra.to_string()
}
})
.collect();
let mut frase = palavras_escolhidas.join(separador);
if com_numero {
let numero: u32 = rng.gen_range(10..999);
frase.push_str(separador);
frase.push_str(&numero.to_string());
}
frase
}
/// Calcula a entropia da senha em bits
pub fn calcular_entropia(comprimento: usize, tamanho_charset: usize) -> f64 {
if tamanho_charset == 0 {
return 0.0;
}
comprimento as f64 * (tamanho_charset as f64).log2()
}
/// Calcula a entropia de uma frase-senha
pub fn calcular_entropia_frase(num_palavras: usize, tamanho_dicionario: usize) -> f64 {
num_palavras as f64 * (tamanho_dicionario as f64).log2()
}
/// Retorna o tamanho do dicionário de palavras
pub fn tamanho_dicionario() -> usize {
PALAVRAS.len()
}
Usamos rand::thread_rng() que fornece um gerador de números aleatórios criptograficamente seguro no modo padrão. A entropia é calculada como log2(tamanho_charset) * comprimento, que nos dá o número de bits de aleatoriedade na senha.
Passo 3: Avaliador de Força de Senha
// src/avaliador.rs
use colored::*;
/// Níveis de força da senha
#[derive(Debug)]
pub enum ForcaSenha {
MuitoFraca,
Fraca,
Razoavel,
Forte,
MuitoForte,
}
/// Resultado da avaliação de uma senha
#[derive(Debug)]
pub struct Avaliacao {
pub forca: ForcaSenha,
pub entropia: f64,
pub comprimento: usize,
pub tem_minuscula: bool,
pub tem_maiuscula: bool,
pub tem_digito: bool,
pub tem_simbolo: bool,
pub tipos_caracteres: usize,
}
/// Avalia a força de uma senha
pub fn avaliar(senha: &str) -> Avaliacao {
let comprimento = senha.len();
let tem_minuscula = senha.chars().any(|c| c.is_ascii_lowercase());
let tem_maiuscula = senha.chars().any(|c| c.is_ascii_uppercase());
let tem_digito = senha.chars().any(|c| c.is_ascii_digit());
let tem_simbolo = senha.chars().any(|c| !c.is_alphanumeric());
let tipos_caracteres = [tem_minuscula, tem_maiuscula, tem_digito, tem_simbolo]
.iter()
.filter(|&&b| b)
.count();
// Estimar o tamanho do charset usado
let mut tamanho_charset = 0;
if tem_minuscula { tamanho_charset += 26; }
if tem_maiuscula { tamanho_charset += 26; }
if tem_digito { tamanho_charset += 10; }
if tem_simbolo { tamanho_charset += 30; }
let entropia = if tamanho_charset > 0 {
comprimento as f64 * (tamanho_charset as f64).log2()
} else {
0.0
};
let forca = match entropia as u32 {
0..=28 => ForcaSenha::MuitoFraca,
29..=49 => ForcaSenha::Fraca,
50..=69 => ForcaSenha::Razoavel,
70..=99 => ForcaSenha::Forte,
_ => ForcaSenha::MuitoForte,
};
Avaliacao {
forca,
entropia,
comprimento,
tem_minuscula,
tem_maiuscula,
tem_digito,
tem_simbolo,
tipos_caracteres,
}
}
/// Exibe a avaliação formatada no terminal
pub fn exibir_avaliacao(avaliacao: &Avaliacao) {
println!("\n{}", "Avaliação da Senha".bold().cyan());
println!(" Comprimento: {} caracteres", avaliacao.comprimento);
println!(" Entropia: {:.1} bits", avaliacao.entropia);
println!(" Tipos de caracteres: {}/4", avaliacao.tipos_caracteres);
let (texto_forca, barra) = match avaliacao.forca {
ForcaSenha::MuitoFraca => ("Muito Fraca".red().bold(), "##..........".red()),
ForcaSenha::Fraca => ("Fraca".red(), "####........".red()),
ForcaSenha::Razoavel => ("Razoável".yellow(), "######......".yellow()),
ForcaSenha::Forte => ("Forte".green(), "########....".green()),
ForcaSenha::MuitoForte => ("Muito Forte".green().bold(), "############".green().bold()),
};
println!(" Força: {} [{}]", texto_forca, barra);
// Detalhes dos tipos de caracteres
let sim = "Sim".green();
let nao = "Não".red();
println!("\n Minúsculas: {}", if avaliacao.tem_minuscula { &sim } else { &nao });
println!(" Maiúsculas: {}", if avaliacao.tem_maiuscula { &sim } else { &nao });
println!(" Dígitos: {}", if avaliacao.tem_digito { &sim } else { &nao });
println!(" Símbolos: {}", if avaliacao.tem_simbolo { &sim } else { &nao });
// Estimativa de tempo para brute-force (1 bilhão de tentativas por segundo)
let tentativas = 2.0_f64.powf(avaliacao.entropia);
let segundos = tentativas / 1_000_000_000.0;
let tempo = if segundos < 60.0 {
"menos de 1 minuto".to_string()
} else if segundos < 3600.0 {
format!("{:.0} minutos", segundos / 60.0)
} else if segundos < 86400.0 * 365.0 {
format!("{:.1} dias", segundos / 86400.0)
} else if segundos < 86400.0 * 365.0 * 1000.0 {
format!("{:.0} anos", segundos / (86400.0 * 365.0))
} else {
format!("{:.2e} anos", segundos / (86400.0 * 365.0))
};
println!("\n Tempo estimado para quebrar (brute-force): {}", tempo.bold());
}
O avaliador calcula a entropia baseada nos tipos de caracteres presentes e estima o tempo para quebrar a senha por força bruta considerando 1 bilhao de tentativas por segundo.
Passo 4: Integrando no main.rs
// src/main.rs
mod avaliador;
mod cli;
mod gerador;
use clap::Parser;
use cli::{Cli, Comando};
use colored::*;
fn main() {
let cli = Cli::parse();
match cli.comando {
Comando::Gerar {
comprimento,
quantidade,
maiusculas,
minusculas,
digitos,
simbolos,
sem_ambiguos,
caracteres,
} => {
let config = gerador::ConfigGeracao {
comprimento,
maiusculas,
minusculas,
digitos,
simbolos,
sem_ambiguos,
caracteres_personalizados: caracteres,
};
let charset = gerador::construir_charset(&config);
if charset.is_empty() {
eprintln!(
"{} Nenhum conjunto de caracteres selecionado.",
"ERRO:".red().bold()
);
std::process::exit(1);
}
let entropia = gerador::calcular_entropia(comprimento, charset.len());
println!("{}", "Senhas geradas:".bold().cyan());
println!(
" Charset: {} caracteres | Comprimento: {} | Entropia: {:.1} bits\n",
charset.len(),
comprimento,
entropia
);
for i in 1..=quantidade {
let senha = gerador::gerar_senha(&charset, comprimento);
if quantidade > 1 {
println!(" {}. {}", i, senha.bold());
} else {
println!(" {}", senha.bold());
}
}
// Avaliar a primeira senha como referência
println!();
let primeira = gerador::gerar_senha(&charset, comprimento);
let avaliacao = avaliador::avaliar(&primeira);
avaliador::exibir_avaliacao(&avaliacao);
}
Comando::Frase {
palavras,
separador,
capitalizar,
com_numero,
} => {
let frase = gerador::gerar_frase(palavras, &separador, capitalizar, com_numero);
let entropia = gerador::calcular_entropia_frase(
palavras,
gerador::tamanho_dicionario(),
);
println!("{}", "Frase-senha gerada:".bold().cyan());
println!("\n {}", frase.bold());
println!(
"\n Palavras: {} | Entropia: {:.1} bits",
palavras, entropia
);
let avaliacao = avaliador::avaliar(&frase);
avaliador::exibir_avaliacao(&avaliacao);
}
Comando::Avaliar { senha } => {
let avaliacao = avaliador::avaliar(&senha);
avaliador::exibir_avaliacao(&avaliacao);
}
}
}
Como Executar
cargo build --release
Exemplos de uso:
# Gerar uma senha padrão de 16 caracteres
./target/release/gerar-senha gerar
# Senha: x$7KmP!qR2vN&fLw
# Entropia: 104.9 bits - Muito Forte
# Gerar 5 senhas de 20 caracteres
./target/release/gerar-senha gerar -c 20 -n 5
# Gerar senha sem caracteres ambíguos
./target/release/gerar-senha gerar -c 12 --sem-ambiguos
# Gerar senha apenas com letras e números (sem símbolos)
./target/release/gerar-senha gerar -c 16 --simbolos false
# Gerar frase-senha com 5 palavras
./target/release/gerar-senha frase -p 5
# Frase: estrela-dragao-montanha-raposa-cristal
# Frase-senha capitalizada com número
./target/release/gerar-senha frase -p 4 -c -n
# Frase: Falcao-Oceano-Trovao-Girassol-742
# Avaliar uma senha existente
./target/release/gerar-senha avaliar "MinhaSenh@123"
# Força: Forte [########....] - Entropia: 75.3 bits
Desafios para Expandir
Cópia para a area de transferencia: Use a crate
clipboardouarboardpara copiar a senha gerada automaticamente, evitando que o usuário precise selecioná-la manualmente.Verificação contra listas de vazamentos: Integre com a API “Have I Been Pwned” (usando hashing k-anonimity) para verificar se a senha ou padrão similar já apareceu em vazamentos de dados.
Senhas pronunciáveis: Implemente um modo que gere senhas que alternem consoantes e vogais, produzindo senhas aleatórias mas que podem ser memorizadas mais facilmente (ex: “bopikaru42”).
Perfis de configuração: Permita que o usuário salve perfis de configuração (ex: “banco”, “email”, “wifi”) com diferentes requisitos de complexidade em um arquivo TOML.
Geração determinística com seed: Adicione a opção de fornecer um seed para gerar senhas reproduzíveis — útil para cenários de teste ou derivação de senhas a partir de uma master key.
Veja Também
- Manipulação de Strings — operações com
Stringe&str - Iteradores em Rust — composição funcional de sequências
- Gerando Hashes — receita para hashing seguro
- Segurança em Rust — como Rust ajuda na segurança de software