Privacidade Por Equipe DevBras

Capa do artigo: Medidor de força de senha que não vaza: calcule no navegador e confira o envio

Medidor de força de senha que não vaza: calcule no navegador e confira o envio

Descubra se o seu medidor manda a senha para fora, troque por cálculo local com biblioteca servida do seu domínio e consulte senhas vazadas sem nunca enviar a senha inteira.

Na tela de cadastro, a pessoa digita a senha e a barra vai do vermelho ao verde. Bonito. Agora abra o DevTools, aba Network, e digite de novo: a cada tecla sai um pedido para api.forca-senha-exemplo.com/check?pw=Ma, ?pw=Mar, ?pw=Marc, até a senha completa. O medidor que parecia segurança entregou a senha, letra por letra, a um serviço que você nem conhece. A resposta curta: o medidor de força precisa rodar inteiro no navegador, sem rede. Se você também quer saber se a senha já apareceu em vazamentos, existe um jeito de consultar que nunca envia a senha nem o hash completo.

O teste definitivo leva trinta segundos: coloque o DevTools em modo offline e digite. Se o medidor parar de funcionar, ele dependia de alguém do outro lado.

Por que medidores acabam mandando a senha para fora

O caminho é quase sempre o mesmo. Alguém procura "medidor de senha jQuery", acha um plugin antigo ou um serviço "gratuito" com API, cola o script e segue. Às vezes o plugin chama um serviço externo para comparar com dicionários. Às vezes a biblioteca vem de um CDN qualquer e, anos depois, o pacote é comprometido. Às vezes ninguém fez nada de errado no medidor, mas uma ferramenta de gravação de sessão ou um script de analytics registra tudo o que é digitado na página, inclusive o campo de senha.

Em todos os casos, o efeito é o mesmo: a senha sai da máquina da pessoa antes mesmo de chegar ao seu servidor. E ela está sendo digitada justamente na página que mais exige cuidado. Como muita gente reaproveita senha, um vazamento aqui abre contas em outros lugares.

Como auditar o medidor que você já tem

  1. Abra a tela de cadastro com a Network aberta. Filtre por Fetch/XHR, digite uma senha de teste e veja se algum pedido sai a cada tecla.
  2. Repita em modo offline. No DevTools, mude a limitação de rede para Offline e digite. O medidor deve continuar funcionando.
  3. Veja de onde vêm os scripts da página. Na aba Sources ou na Network, liste todo arquivo JavaScript de domínio que não é seu.
  4. Procure no código. Busque por keyup, input e chamadas de rede perto do campo de senha.
  5. Confira a gravação de sessão. Se o site usa ferramenta que grava a navegação, confirme que campos de senha são mascarados e, de preferência, que a ferramenta nem roda nessa página.
  6. Restrinja com CSP. Um cabeçalho de política de segurança de conteúdo com connect-src limitado impede que um script mande dados para domínios não autorizados.

Um cabeçalho de partida para a página de cadastro:

Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self'; frame-ancestors 'none'

Com isso, mesmo que um script tente enviar a senha para outro domínio, o navegador bloqueia e registra o erro no console. Teste em homologação antes: CSP restritiva quebra o que depende de terceiro, e é isso mesmo que você quer descobrir.

Como medir no navegador, sem rede

A biblioteca zxcvbn, criada no Dropbox, estima quantas tentativas um atacante precisaria, considerando palavras comuns, nomes, datas, sequências de teclado e substituições como "@" no lugar de "a". Ela devolve uma nota de 0 a 4 e dicas em texto. Existe também um fork mantido, o zxcvbn-ts, com a mesma ideia e uma API um pouco diferente. Instale pelo gerenciador de pacotes e sirva do seu próprio domínio, junto com o resto do bundle, nunca de um CDN aleatório.

<label for="nova-senha">Senha</label>
<input type="password" id="nova-senha" name="senha" autocomplete="new-password" minlength="8" required>
<meter id="forca-senha" min="0" max="4" value="0"></meter>
<p id="dica-senha" aria-live="polite"></p>
const campo = document.getElementById('nova-senha');
const barra = document.getElementById('forca-senha');
const dica = document.getElementById('dica-senha');
const email = document.getElementById('email');
let zxcvbn = null;

campo.addEventListener('focus', async () => {
  if (zxcvbn) return;
  try {
    zxcvbn = (await import('zxcvbn')).default;
  } catch (err) {
    dica.textContent = 'Use uma frase longa, com palavras que não tenham relação entre si.';
  }
}, { once: true });

campo.addEventListener('input', () => {
  if (!zxcvbn) return;
  const resultado = zxcvbn(campo.value.slice(0, 100), [email ? email.value : '']);
  barra.value = resultado.score;
  dica.textContent = resultado.feedback.warning || resultado.feedback.suggestions[0] || '';
});

A importação dinâmica carrega a biblioteca, que é grande por causa dos dicionários, só quando a pessoa entra no campo, e do seu próprio servidor. O corte em 100 caracteres evita que uma entrada enorme deixe a análise lenta. O e-mail passado como segundo argumento faz a nota cair se a senha contiver o próprio endereço. Os critérios de nota e por que barra verde não garante senha boa são assunto do post medidor de força da senha.

Como checar vazamento sem enviar a senha

Saber se a senha já apareceu em vazamentos é útil, e as diretrizes do NIST (SP 800-63B) recomendam priorizar comprimento, não impor regras de composição e comparar com listas de senhas comprometidas. O serviço Pwned Passwords faz isso com um modelo chamado k-anonimato: você calcula o SHA-1 da senha, envia só os cinco primeiros caracteres do hash e recebe a lista de todos os finais que começam com eles. A comparação acontece do seu lado.

Faça no servidor, no momento do cadastro ou da troca de senha:

const crypto = require('crypto');

async function senhaJaVazou(senha) {
  const hash = crypto.createHash('sha1').update(senha, 'utf8').digest('hex').toUpperCase();
  const prefixo = hash.slice(0, 5);
  const sufixo = hash.slice(5);

  const resposta = await fetch('https://api.pwnedpasswords.com/range/' + prefixo, {
    headers: { 'User-Agent': 'loja-exemplo-cadastro' },
    signal: AbortSignal.timeout(3000)
  });
  if (!resposta.ok) {
    throw new Error('Pwned Passwords respondeu ' + resposta.status);
  }
  const corpo = await resposta.text();
  return corpo.split('\n').some((linha) => linha.split(':')[0] === sufixo);
}

module.exports = { senhaJaVazou };

Decida antes o que acontece se o serviço cair: em geral, aceitar o cadastro e registrar a falha é melhor do que impedir todo mundo de criar conta. E esse hash é só para a consulta; a senha continua sendo guardada com algoritmo próprio para senhas, como mostrado em login com senha protegida. A diferença entre esse desenho e o medidor do começo é toda a questão: aqui, nenhum terceiro recebe a senha nem algo que permita reconstruí-la.

Erros comuns

  • Medidor que consulta API a cada tecla. A senha completa vai para um terceiro, e às vezes fica no log de acesso dele.
  • Biblioteca de CDN sem controle. Se o pacote for comprometido, o script passa a ler a senha em todas as páginas que o carregam.
  • Gravação de sessão na página de cadastro. A ferramenta registra digitação e o campo de senha pode não estar mascarado.
  • Exigir só caractere especial. "Senha@123" passa na regra e cai em segundos num ataque de dicionário; comprimento conta mais.
  • Bloquear colar no campo. Atrapalha o gerenciador de senhas e empurra a pessoa para senhas curtas.
  • Tratar o medidor como validação. Ele é dica; a regra real e o armazenamento seguro ficam no servidor.

Checklist

  • Com o DevTools em modo offline, o medidor continua funcionando.
  • Nenhum pedido de rede sai enquanto a senha é digitada.
  • A biblioteca do medidor é servida do seu domínio.
  • A página de cadastro tem CSP com connect-src restrito.
  • Ferramentas de gravação de sessão não rodam ou mascaram o campo de senha.
  • A checagem de vazamento usa só o prefixo do hash.
  • Colar no campo de senha é permitido.

Abra agora a sua tela de cadastro, coloque o DevTools em offline e digite uma senha: o medidor continua respondendo, ou você acabou de descobrir para quem ele estava mandando o que as pessoas digitam?

Avalie este artigo

Referência: http://www.jquerymagazine.com.br/artigo.php?id=236