Programação Por Equipe DevBras

Capa do artigo: Política de senha no servidor: o que validar quando a barra de força engana

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?

Avalie este artigo

Referência: https://github.com/danpalmer/jquery.complexify.js