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
- 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.
- Repita em modo offline. No DevTools, mude a limitação de rede para Offline e digite. O medidor deve continuar funcionando.
- 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.
- Procure no código. Busque por
keyup,inpute chamadas de rede perto do campo de senha. - 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.
- Restrinja com CSP. Um cabeçalho de política de segurança de conteúdo com
connect-srclimitado 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-srcrestrito. - 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?
Referência: http://www.jquerymagazine.com.br/artigo.php?id=236