Medidor de força de senha que mede o que importa: tamanho e senha comum
Construa um medidor que valoriza comprimento, recusa senhas famosas e o próprio e-mail, dá dicas úteis e é repetido no servidor, onde a regra vale de verdade.
No cadastro, a pessoa digita Maria2024! e a barra fica verde: tem maiúscula, número, símbolo e dez caracteres. Uma ferramenta de quebra de senha com regras de "nome + ano + símbolo" testa essa combinação em segundos. Enquanto isso, janela trator morango azul, quatro palavras soltas, fica amarela porque "falta símbolo". A resposta curta: um bom medidor premia comprimento, recusa senhas da lista das mais usadas e senhas que contêm o e-mail ou o nome do site, e explica o que fazer ("use uma frase mais longa"), não qual símbolo acrescentar. E a mesma regra roda no servidor, porque o medidor do navegador é só uma dica.
O exemplo é o cadastro de um sistema de agendamento para salões de beleza. Vamos trocar o medidor que conta tipos de caractere por um que mede o que realmente atrasa um atacante.
Por que contar tipos de caractere engana
A conta ingênua de entropia diz: 10 caracteres escolhidos entre 94 possíveis dão 10 × log2(94), cerca de 65 bits. Seria uma senha excelente, se cada caractere fosse sorteado. Não é. Gente escolhe nome, ano, primeira letra maiúscula e um ponto de exclamação no fim, e quem quebra senha sabe disso.
Faça a conta do jeito do atacante: uns 10 mil nomes comuns, vezes 100 anos prováveis, vezes uns 10 símbolos no final, dá 10 milhões de combinações. Isso são cerca de 23 bits, quase nada para uma placa de vídeo. O medidor que só conta classes de caractere chamaria essa senha de forte.
Agora a frase. Sorteando 4 palavras de uma lista de 7.776 (o tamanho da lista do método Diceware), são 7.776 elevado a 4 combinações, cerca de 52 bits. Com 5 palavras, quase 65 bits, e continua fácil de lembrar. O detalhe que importa: as palavras precisam ser sorteadas, não um verso famoso ou o nome do time.
O que o medidor consegue e não consegue saber
No navegador, você sabe o tamanho, se a senha está numa lista curta de senhas famosas e se ela contém o e-mail digitado. Você não sabe se ela já vazou em outro site; isso exige consultar uma base de vazamentos, assunto de medidor bonito não substitui o vazamento já acontecido. E o medidor jamais deve mandar a senha para um serviço de terceiros, como mostra medidor de força não pode enviar a senha.
Como construir o medidor passo a passo
- Defina o mínimo do servidor primeiro. Este exemplo usa 12 caracteres, sem exigir símbolo.
- No campo, use
type="password"eautocomplete="new-password", para o gerenciador de senhas sugerir uma senha forte. - Avalie em ordem: vazio, senha comum, contém o e-mail, curta demais, poucos caracteres diferentes, ano no meio.
- Mostre uma nota de 0 a 4 e uma dica em texto, numa região que o leitor de tela anuncia.
- Repita as mesmas regras no servidor, que é quem recusa de verdade.
<label for="senha">Senha</label>
<input id="senha" name="senha" type="password"
autocomplete="new-password" minlength="12" required
aria-describedby="dica-senha">
<meter id="forca" min="0" max="4" low="2" high="3" optimum="4" value="0"></meter>
<p id="dica-senha" aria-live="polite">Use pelo menos 12 caracteres. Uma frase com palavras soltas funciona bem.</p>
const COMUNS = new Set([
'123456', '12345678', '123456789', 'senha', 'senha123',
'password', 'qwerty', 'abc123', 'brasil', 'salaobeleza'
]);
const MINIMO = 12;
function avaliar(senha, email) {
const s = senha.toLowerCase();
const usuario = email.split('@')[0].toLowerCase();
if (senha.length === 0) {
return { nota: 0, dica: 'Use pelo menos ' + MINIMO + ' caracteres.' };
}
if (COMUNS.has(s)) {
return { nota: 0, dica: 'Essa senha está entre as mais usadas. Escolha outra.' };
}
if (usuario.length >= 3 && s.includes(usuario)) {
return { nota: 0, dica: 'Tire o seu e-mail da senha.' };
}
if (senha.length < MINIMO) {
const faltam = MINIMO - senha.length;
return { nota: 1, dica: 'Faltam ' + faltam + ' caracteres. Tente uma frase com palavras soltas.' };
}
if (new Set(s).size < 5) {
return { nota: 1, dica: 'Muitos caracteres repetidos. Varie mais.' };
}
let nota = senha.length >= 20 ? 4 : senha.length >= 16 ? 3 : 2;
if (/(19|20)[0-9]{2}/.test(senha)) {
nota = Math.max(2, nota - 1);
return { nota, dica: 'Anos são fáceis de adivinhar. Troque por outra palavra.' };
}
const dicas = ['', '', 'Boa. Mais uma palavra deixa melhor.', 'Muito boa.', 'Excelente.'];
return { nota, dica: dicas[nota] };
}
const campo = document.getElementById('senha');
const medidor = document.getElementById('forca');
const dica = document.getElementById('dica-senha');
const campoEmail = document.getElementById('email');
campo.addEventListener('input', () => {
const r = avaliar(campo.value, campoEmail ? campoEmail.value : '');
medidor.value = r.nota;
dica.textContent = r.dica;
});
Com essas regras, Maria2024! recebe nota 1 e a dica "faltam 2 caracteres", e janela trator morango azul, com 26 caracteres, recebe nota 4. A lista de senhas comuns aqui é curta para caber no exemplo; no servidor, use uma lista de milhares, como as listas públicas de senhas mais usadas do repositório SecLists. Se quiser uma estimativa mais sofisticada no navegador, a biblioteca zxcvbn, criada no Dropbox, reconhece padrões como datas, sequências de teclado e palavras de dicionário, e devolve nota de 0 a 4 com sugestões.
A regra que vale fica no servidor
<?php
function problemaNaSenha(string $senha, string $email, array $comuns): ?string
{
if (mb_strlen($senha) < 12) {
return 'A senha precisa ter pelo menos 12 caracteres.';
}
if (strlen($senha) > 72) {
return 'A senha pode ter no máximo 72 bytes.';
}
$minuscula = mb_strtolower($senha);
if (isset($comuns[$minuscula])) {
return 'Essa senha está entre as mais usadas.';
}
$usuario = mb_strtolower((string) strstr($email, '@', true));
if (mb_strlen($usuario) >= 3 && str_contains($minuscula, $usuario)) {
return 'A senha não pode conter o seu e-mail.';
}
return null;
}
// $comuns: array_flip(file('/var/app/dados/senhas-comuns.txt', FILE_IGNORE_NEW_LINES))
$erro = problemaNaSenha($_POST['senha'] ?? '', $_POST['email'] ?? '', $comuns);
if ($erro === null) {
$hash = password_hash($_POST['senha'], PASSWORD_DEFAULT);
}
O limite de 72 bytes não é capricho: o PASSWORD_DEFAULT do PHP usa bcrypt, que ignora o que passa de 72 bytes. Sem avisar, uma frase longa demais teria só o começo protegido. Se você usa Argon2id (PASSWORD_ARGON2ID), esse limite não existe, e pode aceitar frases maiores. O resto do fluxo de senha está em login com senha protegida.
Se você também quiser recusar senhas que já vazaram, faça a consulta no servidor, no momento do cadastro, e não a cada tecla. A API Pwned Passwords usa um modelo de k-anonimato: você calcula o SHA-1 da senha e envia só os 5 primeiros caracteres do hash; a resposta traz os finais de hash que começam assim, e a comparação acontece do seu lado. A senha nunca sai do servidor.
Erros comuns
- Pontuar maiúscula, número e símbolo. O medidor ensina o padrão
Nome2024!, que é exatamente o que as ferramentas de quebra testam primeiro. - Exigir símbolo obrigatório. A pessoa acrescenta "!" no fim e anota a senha num papel. Comprimento e lista de bloqueio protegem mais. As diretrizes do NIST para autenticação, na SP 800-63B, recomendam não impor regras de composição.
- Bloquear colar no campo. Quem usa gerenciador de senhas, que gera as melhores senhas, é justamente quem é impedido.
- Validar só no navegador. Basta desligar o JavaScript ou mandar o POST direto para cadastrar
123456. - Dica genérica como "senha fraca". A pessoa não sabe o que mudar e tenta combinações aleatórias até a barra ficar verde.
- Ignorar o limite do bcrypt. Senhas acima de 72 bytes são aceitas e só o começo vale, sem ninguém saber.
Checklist
- O medidor valoriza comprimento e não exige tipos de caractere.
- Senhas comuns e senhas com o e-mail recebem nota zero.
- A dica diz o que fazer, em texto, numa região anunciada ao leitor de tela.
- O campo usa
autocomplete="new-password"e permite colar. - O servidor repete as regras e recusa sozinho, mesmo sem JavaScript.
- A senha nunca sai do navegador para outro serviço durante a digitação.
- O limite máximo combina com o algoritmo de hash usado.
Abra agora o cadastro do seu sistema e digite duas senhas: Senha@2024 e janela trator morango azul. Qual das duas o medidor chama de forte, e o servidor aceitaria a primeira se o JavaScript estivesse desligado?
Referência: https://www.danpalmer.me/jquery-complexify