Política de senha no servidor: o que validar quando a barra de força engana
A barra colorida é um palpite no navegador. A regra que vale fica no servidor: comprimento, lista de senhas vazadas, nada de exigir símbolo. Exemplo completo em PHP, sem mandar a senha para fora.
No cadastro da loja, a barra de força marca "fraca" em vermelho para cafe com leite na varanda e "forte" em verde para Loja@2026. A cliente obedece a barra, cria a senha curta com arroba, esquece na semana seguinte e abre chamado. A senha "forte" está em qualquer lista de ataque, porque é exatamente o padrão que todo mundo usa quando o sistema exige maiúscula, número e símbolo.
A resposta curta: a barra no navegador é ajuda visual; a política de senha mora no servidor. Exija comprimento mínimo, aceite espaços e caracteres acentuados, recuse senhas comuns e vazadas e não obrigue a pessoa a colocar símbolo. Se a biblioteca de complexidade ensina o contrário disso, troque a biblioteca ou tire a barra.
Por que medir complexidade dá o resultado errado
Bibliotecas como a jquery.complexify, que originou este post, calculam uma nota a partir de quantos tipos de caractere aparecem: minúsculas, maiúsculas, números, símbolos. A conta assume que cada caractere foi sorteado. Pessoas não sorteiam. Quem precisa de maiúscula põe na primeira letra; quem precisa de número põe o ano no fim; quem precisa de símbolo usa @ ou !. Os programas de ataque testam exatamente essas variações primeiro.
Já uma frase de quatro ou cinco palavras comuns fica longa o bastante para que testar todas as combinações seja inviável, mesmo sem símbolo. E é fácil de lembrar, o que reduz a senha anotada em papel e o reaproveitamento da mesma senha em vários sites.
A NIST SP 800-63B, referência americana muito usada para autenticação, vai na mesma direção: recomenda priorizar comprimento, aceitar senhas longas e espaços, permitir colar (o que favorece gerenciadores de senha), conferir a senha contra listas de senhas comprometidas e não impor regras de composição nem troca periódica sem indício de vazamento. O mínimo ali é de 8 caracteres, e a revisão mais recente pede mais quando a senha é o único fator de autenticação. A política completa da revisão 4, com o que virou obrigação e um plano para largar a troca de 90 dias, está em política de senha NIST: regras obrigatórias e migração.
Há ainda o problema da origem: a validação no navegador pode ser ignorada por quem chama a API direto. Se a regra existe só em JavaScript, ela não existe.
Como montar a política no servidor
A validação em PHP
<?php
// politica-senha.php
function normalizarSenha(string $senha): string
{
if (class_exists('Normalizer')) {
$normal = Normalizer::normalize($senha, Normalizer::FORM_KC);
if ($normal !== false) {
return $normal;
}
}
return $senha;
}
function senhaVazada(string $senha): ?bool
{
$hash = strtoupper(sha1($senha));
$prefixo = substr($hash, 0, 5);
$sufixo = substr($hash, 5);
$contexto = stream_context_create([
'http' => ['timeout' => 3, 'header' => 'User-Agent: loja-cadastro'],
]);
$resposta = @file_get_contents(
'https://api.pwnedpasswords.com/range/' . $prefixo, false, $contexto);
if ($resposta === false) {
return null; // serviço fora do ar: não bloqueia o cadastro
}
return str_contains($resposta, $sufixo . ':');
}
function validarSenha(string $senha, string $email): array
{
$erros = [];
$senha = normalizarSenha($senha);
$tamanho = mb_strlen($senha, 'UTF-8');
if ($tamanho < 12) {
$erros[] = 'Use pelo menos 12 caracteres. Uma frase com espaços vale.';
}
if ($tamanho > 128) {
$erros[] = 'Use no máximo 128 caracteres.';
}
$minuscula = mb_strtolower($senha, 'UTF-8');
$comuns = array_flip(file(__DIR__ . '/senhas-comuns.txt',
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
if (isset($comuns[$minuscula])) {
$erros[] = 'Essa senha é muito usada. Escolha outra.';
}
$usuario = mb_strtolower(strstr($email, '@', true) ?: $email, 'UTF-8');
if (mb_strlen($usuario) >= 4 && str_contains($minuscula, $usuario)) {
$erros[] = 'Não use o seu e-mail dentro da senha.';
}
if (!$erros && senhaVazada($senha) === true) {
$erros[] = 'Essa senha já apareceu em vazamentos de outros sites. Escolha outra.';
}
return $erros;
}
E no cadastro:
<?php
require __DIR__ . '/politica-senha.php';
$erros = validarSenha($_POST['senha'] ?? '', $_POST['email'] ?? '');
if ($erros) {
http_response_code(422);
echo json_encode(['erros' => $erros]);
exit;
}
$hash = password_hash(normalizarSenha($_POST['senha']), PASSWORD_DEFAULT);
// grava $hash na coluna senha_hash; a senha em si não vai para o banco
Três detalhes que fazem diferença. Primeiro, a consulta de vazamento usa o modelo de k-anonimato do serviço Pwned Passwords: só os cinco primeiros caracteres do SHA-1 saem do seu servidor, a resposta traz centenas de sufixos possíveis, e a comparação acontece localmente. A senha não viaja. Segundo, se o serviço não responder, o cadastro segue com as outras regras; a lista local continua protegendo contra o óbvio. Terceiro, a mesma normalização tem de ser aplicada no login, antes do password_verify, senão a senha acentuada não confere.
Sobre o hash: com PASSWORD_DEFAULT, o PHP usa bcrypt, que considera só os primeiros 72 bytes da senha. Para frases muito longas com acentos, que ocupam mais de um byte por letra, isso pode cortar o final. Se o seu PHP oferece PASSWORD_ARGON2ID, ele não tem esse limite. O guia de login com senha protegida cobre o resto do fluxo de hash e verificação.
E a barra de força no navegador
Ela pode continuar, desde que ensine a mesma coisa que o servidor exige. Uma barra que premia comprimento e penaliza padrões comuns ajuda; uma que só conta tipos de caractere atrapalha. Antes de manter um script antigo, abra a aba de rede, digite uma senha e confira se nenhuma requisição sai com ela. O risco de um medidor enviar a senha para terceiros está detalhado em medidor de força de senha não pode enviar a senha, e a escolha da barra em si, em barra verde não é senha boa.
Erros comuns
Checklist
Teste agora no seu cadastro: cafe com leite na varanda passa, e Loja@2026 é a única que o sistema aceita sem reclamar?
Referência: https://github.com/danpalmer/jquery.complexify.js